Mooeypoo has uploaded a new change for review.
https://gerrit.wikimedia.org/r/234451
Change subject: [wip] Use viewmodel in Echo's front end
......................................................................
[wip] Use viewmodel in Echo's front end
Change-Id: Ia3b220b373b70b1419fecd6d0edb1d7df1af4417
---
M Resources.php
M modules/ext.echo.init.js
M modules/ooui/mw.echo.ui.NotificationBadgeWidget.js
M modules/ooui/mw.echo.ui.NotificationOptionWidget.js
M modules/ooui/mw.echo.ui.NotificationsWidget.js
A modules/viewmodel/mw.echo.dm.List.js
A modules/viewmodel/mw.echo.dm.NotificationItem.js
A modules/viewmodel/mw.echo.dm.NotificationList.js
A modules/viewmodel/mw.echo.dm.NotificationsModel.js
A modules/viewmodel/mw.echo.dm.js
10 files changed, 1,010 insertions(+), 383 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo
refs/changes/51/234451/1
diff --git a/Resources.php b/Resources.php
index 8804322..6b73324 100644
--- a/Resources.php
+++ b/Resources.php
@@ -54,6 +54,7 @@
),
'dependencies' => array(
'ext.echo.nojs',
+ 'ext.echo.dm',
'oojs-ui',
'ext.echo.logger',
'mediawiki.api',
@@ -72,6 +73,19 @@
),
'targets' => array( 'desktop', 'mobile' ),
),
+ 'ext.echo.dm' => $echoResourceTemplate + array(
+ 'scripts' => array(
+ 'viewmodel/mw.echo.dm.js',
+ 'viewmodel/mw.echo.dm.NotificationItem.js',
+ 'viewmodel/mw.echo.dm.List.js',
+ 'viewmodel/mw.echo.dm.NotificationList.js',
+ 'viewmodel/mw.echo.dm.NotificationsModel.js',
+ ),
+ 'dependencies' => array(
+ '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
diff --git a/modules/ext.echo.init.js b/modules/ext.echo.init.js
index d0d9c2e..af57e2c 100644
--- a/modules/ext.echo.init.js
+++ b/modules/ext.echo.init.js
@@ -28,7 +28,7 @@
if ( $existingMessageLink.length ) {
mw.echo.ui.messageWidget = new
mw.echo.ui.NotificationBadgeWidget( {
type: 'message',
- markReadWhenSeen: false,
+ markReadWhenSeen: true,//false,
numItems: numMessages,
hasUnread: hasUnreadMessages,
badgeIcon: 'speechBubble',
diff --git a/modules/ooui/mw.echo.ui.NotificationBadgeWidget.js
b/modules/ooui/mw.echo.ui.NotificationBadgeWidget.js
index 989b765..2aced3b 100644
--- a/modules/ooui/mw.echo.ui.NotificationBadgeWidget.js
+++ b/modules/ooui/mw.echo.ui.NotificationBadgeWidget.js
@@ -39,10 +39,25 @@
buttonFlags.push( 'unseen' );
}
- this.notificationsWidget = new mw.echo.ui.NotificationsWidget( {
+ // View model
+ this.notificationsModel = new mw.echo.dm.NotificationsModel( {
type: this.type,
- markReadWhenSeen: this.markReadWhenSeen
+ limit: 25,
+ userLang: mw.config.get( 'wgUserLanguage' )
} );
+
+ // Notifications widget
+ this.notificationsWidget = new mw.echo.ui.NotificationsWidget(
+ this.notificationsModel,
+ {
+ type: this.type,
+ markReadWhenSeen: this.markReadWhenSeen
+ }
+ );
+
+ // Mixin constructors
+ OO.ui.mixin.PendingElement.call( this, config );
+ this.setPendingElement( this.notificationsWidget.$element );
// Footer
allNotificationsButton = new OO.ui.ButtonWidget( {
@@ -98,6 +113,7 @@
label: mw.msg( 'echo-mark-all-as-read' ),
classes: [
'mw-echo-ui-notificationsWidget-markAllReadButton' ]
} );
+
// Hide the close button
this.popup.closeButton.toggle( false );
// Add the 'mark all as read' button to the header
@@ -105,12 +121,12 @@
this.markAllReadButton.toggle( !this.markReadWhenSeen &&
this.hasUnread );
// Events
- this.notificationsWidget.connect( this, {
- seen: 'onNotificationsSeen',
- update: 'onNotificationsUpdate',
- readChange: 'onNotificationsReadChange'
- } );
this.markAllReadButton.connect( this, { click:
'onMarkAllReadButtonClick' } );
+ this.notificationsModel.connect( this, {
+ updateSeenTime: 'onModelUpdateSeenTime',
+ unseenChange: 'onModelUnseenChange',
+ unreadChange: 'onModelUnreadChange'
+ } );
this.$element
.addClass(
@@ -122,49 +138,35 @@
/* Initialization */
OO.inheritClass( mw.echo.ui.NotificationBadgeWidget,
OO.ui.PopupButtonWidget );
+ OO.mixinClass( mw.echo.ui.NotificationBadgeWidget,
OO.ui.mixin.PendingElement );
+
+ mw.echo.ui.NotificationBadgeWidget.prototype.onModelUpdateSeenTime =
function () {
+ this.setFlags( { unseen: false } );
+ };
+
+ /**
+ * Respond to model unseen change
+ *
+ * @param {mw.echo.dm.NotificationItems[]} unseenNotifications Array of
unseen notifications
+ */
+ mw.echo.ui.NotificationBadgeWidget.prototype.onModelUnseenChange =
function ( unseenNotifications ) {
+ this.setFlags( { unseen: !!unseenNotifications.length } );
+ };
+
+ /**
+ * Respond to model unread change
+ *
+ * @param {mw.echo.dm.NotificationItems[]} unreadNotifications Array of
unread notifications
+ */
+ mw.echo.ui.NotificationBadgeWidget.prototype.onModelUnreadChange =
function ( unreadNotifications ) {
+ this.setLabel( String( unreadNotifications.length ) );
+ };
/**
* Respond to 'mark all as read' button click
*/
mw.echo.ui.NotificationBadgeWidget.prototype.onMarkAllReadButtonClick =
function () {
- this.notificationsWidget.markAllRead()
- .then( this.updateUnreadCount.bind( this ) );
- };
-
- /**
- * Respond to seen time change
- */
- mw.echo.ui.NotificationBadgeWidget.prototype.onNotificationsSeen =
function () {
- // Change the status of the button
- this.setFlags( { unseen: false } );
- };
-
- /**
- * Respond to a change in notification read state
- */
- mw.echo.ui.NotificationBadgeWidget.prototype.onNotificationsReadChange
= function ( unreadCount ) {
- this.updateUnreadCount( unreadCount );
- };
-
- /**
- * Update unread count and state
- *
- * @param {number} unreadCount Unread notification count
- */
- mw.echo.ui.NotificationBadgeWidget.prototype.updateUnreadCount =
function ( unreadCount ) {
- var hasUnread = unreadCount > 0;
-
- this.setLabel( String( unreadCount ) );
- this.setFlags( { unseen:
!!this.notificationsWidget.getUnseenCount() } );
- this.hasUnread = hasUnread;
- this.markAllReadButton.toggle( hasUnread );
- };
-
- /**
- * Respond to update event of the notifications widget
- */
- mw.echo.ui.NotificationBadgeWidget.prototype.onNotificationsUpdate =
function () {
- this.updateUnreadCount(
this.notificationsWidget.getUnreadCount() );
+ this.notificationsModel.markAllRead();
};
/**
@@ -184,50 +186,33 @@
this.type
);
- // Refresh the notifications
if ( !this.updated ) {
- this.notificationsWidget.fetchNotifications()
- .then( function ( optionWidgets ) {
- var i, len,
- idArray = [];
-
+ this.pushPending();
+ this.notificationsModel.fetchNotifications()
+ .then( function ( idArray ) {
// Clip again
widget.popup.clip();
-
- // Gather ids
- for ( i = 0, len =
optionWidgets.length; i < len; i++ ) {
- idArray.push(
optionWidgets[i].getData() );
- }
// Log impressions
mw.echo.logger.logNotificationImpressions( this.type, idArray,
mw.echo.Logger.static.context.popup );
- // Mark notifications as 'read' if
markReadWhenSeen is set to true
+ // // Mark notifications as 'read' if
markReadWhenSeen is set to true
if ( widget.markReadWhenSeen ) {
- return
widget.notificationsWidget.markAllRead()
- .then(
widget.updateUnreadCount.bind( widget ) );
+ return
widget.notificationsModel.markAllRead();
}
} )
.then( function () {
// Update seen time
-
widget.notificationsWidget.updateSeenTime();
+
widget.notificationsModel.updateSeenTime();
+ } )
+ .always( function () {
+ widget.popPending();
} );
this.updated = true;
} else {
// Update seen time
- this.notificationsWidget.updateSeenTime();
+ widget.notificationsModel.updateSeenTime();
}
- };
-
- /**
- * Update unread count of the notifications in this widget
- *
- * @return {jQuery.Promise} jQuery promise that's resolved when the
unread count is fetched
- * and the badge label is updated.
- */
- mw.echo.ui.NotificationBadgeWidget.prototype.fetchUnreadCountFromApi =
function () {
- return this.notificationsWidget.fetchUnreadCountFromApi()
- .then( this.updateUnreadCount.bind( this ) );
};
} )( mediaWiki, jQuery );
diff --git a/modules/ooui/mw.echo.ui.NotificationOptionWidget.js
b/modules/ooui/mw.echo.ui.NotificationOptionWidget.js
index d181229..a6d011e 100644
--- a/modules/ooui/mw.echo.ui.NotificationOptionWidget.js
+++ b/modules/ooui/mw.echo.ui.NotificationOptionWidget.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function ( mw, $ ) {
/**
* Notification option widget for echo popup.
*
@@ -7,17 +7,15 @@
*
* @constructor
* @param {Object} [config] Configuration object
- * @cfg {boolean} [read=false] State the read state of the option
- * @cfg {boolean} [seen=false] State the seen state of the option
* @cfg {boolean} [markReadWhenSeen=false] This option is marked as
read when it is viewed
- * @cfg {string} [link] The link this option leads to
- * @cfg {number} [timestamp=now] The timestamp of the notification
*/
- mw.echo.ui.NotificationOptionWidget = function
MwEchoUiNotificationOptionWidget( config ) {
+ mw.echo.ui.NotificationOptionWidget = function
MwEchoUiNotificationOptionWidget( model, config ) {
config = config || {};
+ this.model = model;
+
// Parent constructor
- mw.echo.ui.NotificationOptionWidget.parent.call( this, config );
+ mw.echo.ui.NotificationOptionWidget.parent.call( this,
$.extend( { data: this.model.getId() }, config ) );
this.markAsReadButton = new OO.ui.ButtonWidget( {
icon: 'close',
@@ -25,13 +23,12 @@
classes: [
'mw-echo-ui-notificationOptionWidget-markAsReadButton' ]
} );
- this.toggleRead( !!config.read );
- this.toggleSeen( !!config.seen );
+ this.setLabel( this.model.getContent() );
+
+ this.toggleRead( this.model.isRead() );
+ this.toggleSeen( this.model.isSeen() );
this.markReadWhenSeen = !!config.markReadWhenSeen;
- this.link = config.link;
-
- this.setTimestamp( config.timestamp || ( Date.now() / 1000 ) );
// Events
this.markAsReadButton.connect( this, { click: [ 'emit',
'markAsRead' ] } );
@@ -47,6 +44,11 @@
this.$element.addClass(
'mw-echo-ui-notificationOptionWidget-markReadWhenSeen' );
}
+ // Events
+ this.model.connect( this, {
+ seen: 'toggleSeen',
+ read: 'toggleRead'
+ } );
};
/* Initialization */
@@ -87,37 +89,6 @@
this.$element
.toggleClass(
'mw-echo-ui-notificationOptionWidget-unseen', !this.seen );
-
- if ( this.markReadWhenSeen && seen ) {
- this.toggleRead( true );
- }
- };
-
- /**
- * Set the notification timestamp
- *
- * @param {number} timestamp Notification timestamp
- */
- mw.echo.ui.NotificationOptionWidget.prototype.setTimestamp = function (
timestamp ) {
- this.timestamp = timestamp;
- };
-
- /**
- * Get the notification timestamp
- *
- * @return {number} Notification timestamp
- */
- mw.echo.ui.NotificationOptionWidget.prototype.getTimestamp = function
() {
- return this.timestamp;
- };
-
- /**
- * Set the notification link
- *
- * @param {string} link Notification link
- */
- mw.echo.ui.NotificationOptionWidget.prototype.setLink = function ( link
) {
- this.link = link;
};
/**
@@ -125,7 +96,24 @@
*
* @return {string} Notification link
*/
- mw.echo.ui.NotificationOptionWidget.prototype.getLink = function () {
- return this.link;
+ mw.echo.ui.NotificationOptionWidget.prototype.getModel = function () {
+ return this.model;
};
-} )( mediaWiki );
+
+ /**
+ * Get the notification link
+ *
+ * @return {string} Notification link
+ */
+ mw.echo.ui.NotificationOptionWidget.prototype.getPrimaryLink = function
() {
+ return this.model.getPrimaryLink();
+ };
+
+ /**
+ * Disconnect events when widget is destroyed.
+ */
+ mw.echo.ui.NotificationOptionWidget.prototype.destroy = function () {
+ this.model.disconnect( this );
+ };
+
+} )( mediaWiki, jQuery );
diff --git a/modules/ooui/mw.echo.ui.NotificationsWidget.js
b/modules/ooui/mw.echo.ui.NotificationsWidget.js
index a5b083f..f15306a 100644
--- a/modules/ooui/mw.echo.ui.NotificationsWidget.js
+++ b/modules/ooui/mw.echo.ui.NotificationsWidget.js
@@ -6,85 +6,123 @@
* @extends OO.ui.Widget
*
* @constructor
+ * @param {mw.echo.dm.NotificationsModel} model Notifications view model
* @param {Object} [config] Configuration object
- * @cfg {string} [type='alert'] Notification type
* @cfg {boolean} [markReadWhenSeen=false] State whether the
notifications are all
* marked as read when they are seen.
- * @cfg {number} [limit=15] Notification limit
*/
- mw.echo.ui.NotificationsWidget = function MwEchoUiNotificationsWidget(
config ) {
+ mw.echo.ui.NotificationsWidget = function MwEchoUiNotificationsWidget(
model, config ) {
config = config || {};
- this.seenTime = mw.user.options.get( 'echo-seen-time' );
+ this.model = model;
+
this.markReadWhenSeen = !!config.markReadWhenSeen;
-
- this.type = config.type || 'alert';
- this.limit = config.limit || 25;
-
- this.howManyUnread = 0;
- this.howManyUnseen = 0;
- this.userLang = mw.config.get( 'wgUserLanguage' );
// Parent constructor
mw.echo.ui.NotificationsWidget.parent.call( this, config );
-
- // Mixin constructors
- OO.ui.mixin.PendingElement.call( this, config );
-
- this.api = new mw.Api( { ajax: { cache: false } } );
-
- // Notification list
- this.notifications = new OO.ui.SelectWidget( {
- classes: [ 'mw-echo-ui-notificationsWidget-list' ]
- } );
- this.setPendingElement( this.notifications.$element );
// Dummy 'loading' option widget
this.loadingOptionWidget = new OO.ui.OptionWidget( {
data: null,
classes: [
'mw-echo-ui-notificationsWidget-loadingOption' ]
} );
- this.notifications.addItems( [ this.loadingOptionWidget ] );
+ this.addItems( [ this.loadingOptionWidget ] );
- // Event
- this.notifications.aggregate( { markAsRead:
'notificationMarkAsRead' } );
- this.notifications.connect( this, {
- choose: 'onNotificationChoose',
- notificationMarkAsRead: 'onNotificationMarkAsRead'
+ // Events
+ this.model.connect( this, {
+ notificationSeen: 'onModelNotificationSeen',
+ notificationRead: 'onModelNotificationRead',
+ add: 'onModelNotificationAdd'
+ } );
+ this.connect( this, {
+ choose: 'onNotificationChoose'
} );
this.$element
- .addClass( 'mw-echo-ui-notificationsWidget' )
- .append( this.notifications.$element );
+ .addClass( 'mw-echo-ui-notificationsWidget' );
};
/* Initialization */
- OO.inheritClass( mw.echo.ui.NotificationsWidget, OO.ui.Widget );
- OO.mixinClass( mw.echo.ui.NotificationsWidget,
OO.ui.mixin.PendingElement );
-
- /* Events */
-
- /**
- * @event seen
- * Change the seen timestamp for this user
- *
- * @param {number} seenTime Mediawiki timestamp for when the user has
seen notifications
- */
-
- /**
- * @event update
- * Notifications were updated
- */
-
- /**
- * @event readChange
- * @param {number} howManyUnread How many unread notifications still
exist
- *
- * Notification's read status has changed
- */
+ OO.inheritClass( mw.echo.ui.NotificationsWidget, OO.ui.SelectWidget );
+ // OO.mixinClass( mw.echo.ui.NotificationsWidget,
OO.ui.mixin.PendingElement );
/* Methods */
+
+ mw.echo.ui.NotificationsWidget.prototype.onModelNotificationSeen =
function () {};
+ mw.echo.ui.NotificationsWidget.prototype.onModelNotificationRead =
function () {};
+
+ /**
+ * Respond to model add event
+ *
+ * @param {mw.echo.dm.NotificationItem[]} Added notification items
+ */
+ mw.echo.ui.NotificationsWidget.prototype.onModelNotificationAdd =
function ( notificationItems, index ) {
+ var i, len, itemCount,
+ optionWidgets = [];
+
+ // Add option widgets (in reverse order)
+ for ( i = 0, len = notificationItems.length; i < len; i++ ) {
+ optionWidgets.push(
+ new mw.echo.ui.NotificationOptionWidget(
+ notificationItems[i],
+ {
+ markReadWhenSeen:
this.markReadWhenSeen
+ }
+ )
+ );
+ }
+
+ // Remove dummy option
+ this.removeItems( [ this.loadingOptionWidget ] );
+
+ this.addItems( optionWidgets, index );
+ };
+
+ /**
+ * Respond to model add event
+ *
+ * @param {mw.echo.dm.NotificationItem[]} Removed notification items
+ */
+ mw.echo.ui.NotificationsWidget.prototype.onModelNotificationClear =
function () {
+ var i, len,
+ items = this.getItems();
+
+ // Destroy all the widgets and their events
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ items[i].destroy();
+ }
+
+ this.clearItems();
+
+ // Add dummy option
+ this.addItems( [ this.loadingOptionWidget ] );
+ };
+
+ /**
+ * Respond to model add event
+ *
+ * @param {mw.echo.dm.NotificationItem[]} Removed notification items
+ */
+ mw.echo.ui.NotificationsWidget.prototype.onModelNotificationRemove =
function ( notificationItems ) {
+ var i, len, widget,
+ removalWidgets = [];
+
+ for ( i = 0, len = notificationItems.length; i < len; i++ ) {
+ widget = this.getItemById( notificationItems[i].getId()
);
+ if ( widget ) {
+ widget.destroy();
+ removalWidgets.push( widget );
+ }
+ }
+
+ this.removeItems( removalWidgets );
+
+ if ( !this.getItemCount() ) {
+ // Add dummy option
+ this.addItems( [ this.loadingOptionWidget ] );
+ }
+ };
/**
* Respond to notification 'mark as read' event
@@ -94,67 +132,24 @@
* @fires readChange
*/
mw.echo.ui.NotificationsWidget.prototype.onNotificationMarkAsRead =
function ( notification ) {
- var widget = this,
- id = notification ? notification.getData() : null,
- data = {
- action: 'echomarkread',
- uselang: this.userLang,
- list: id
- };
+ // var widget = this,
+ // id = notification ? notification.getData() : null,
+ // data = {
+ // action: 'echomarkread',
+ // uselang: this.userLang,
+ // list: id
+ // };
- if ( !id ) {
- return $.Deferred().reject().promise();
- }
+ // if ( !id ) {
+ // return $.Deferred().reject().promise();
+ // }
- return this.api.postWithToken( 'edit', data )
- .then( function () {
- notification.toggleRead( true );
- widget.howManyUnread--;
- widget.emit( 'readChange', widget.howManyUnread
);
- } );
- };
-
- /**
- * Fetch the unread count from the API
- *
- * @return {jQuery.Promise} Promise that is resolved with the unread
notification count
- */
- mw.echo.ui.NotificationsWidget.prototype.fetchUnreadCountFromApi =
function () {
- var widget = this,
- apiData = {
- action: 'query',
- meta: 'notifications',
- notsections: this.type,
- notmessageunreadfirst: 1,
- notlimit: this.limit,
- notprop: 'index|count',
- uselang: this.userLang
- };
-
- return this.api.get( apiData )
- .then( function ( result ) {
- var unread = OO.getProp( result.query,
'notifications', 'rawcount' ) || 0;
- widget.howManyUnread = unread;
- return widget.howManyUnread;
- } );
- };
-
- /**
- * Get the stored number of unread notifications.
- *
- * @return {number} Number of unread notifications
- */
- mw.echo.ui.NotificationsWidget.prototype.getUnreadCount = function () {
- return this.howManyUnread;
- };
-
- /**
- * Get the stored number of unseen notifications.
- *
- * @return {number} Number of unseen notifications
- */
- mw.echo.ui.NotificationsWidget.prototype.getUnseenCount = function () {
- return this.howManyUnseen;
+ // return this.api.postWithToken( 'edit', data )
+ // .then( function () {
+ // notification.toggleRead( true );
+ // widget.howManyUnread--;
+ // widget.emit( 'readChange', widget.howManyUnread
);
+ // } );
};
/**
@@ -180,102 +175,13 @@
};
/**
- * Fetch notifications from the API and update the notifications list.
- *
- * @return {jQuery.Promise} A promise that resolves after the
notifications
- * have been populated, or rejects when there is a failed api request.
- */
- mw.echo.ui.NotificationsWidget.prototype.fetchNotifications = function
() {
- var widget = this,
- apiData = {
- action: 'query',
- meta: 'notifications',
- notsections: this.type,
- notmessageunreadfirst: 1,
- notformat: 'flyout',
- notlimit: this.limit,
- notprop: 'index|list|count',
- uselang: this.userLang
- };
-
- this.pushPending();
- this.clearNotifications();
-
- return this.api.get( apiData )
- .then( function ( result ) {
- var notifData, i, len, $content, wasSeen,
wasRead,
- optionWidgets = [],
- data = result.query.notifications;
-
- for ( i = 0, len = data.index.length; i < len;
i++ ) {
- notifData = data.list[ data.index[i] ];
- // TODO: This should really be
formatted better, and the OptionWidget
- // should be the one that displays
whatever icon relates to this notification
- // according to its type.
- $content = $( $.parseHTML(
notifData['*'] ) );
-
- wasRead = !!notifData.read;
- wasSeen = notifData.timestamp.mw <=
widget.getSeenTime();
-
- if ( !wasRead ) {
- widget.howManyUnread++;
- }
- if ( !wasSeen ) {
- widget.howManyUnseen++;
- }
- optionWidgets.push( new
mw.echo.ui.NotificationOptionWidget( {
- data: notifData.id,
- label: $content,
- read: wasRead,
- seen: wasSeen,
- markReadWhenSeen:
!!widget.markReadWhenSeen,
- timestamp:
notifData.timestamp.mw,
- category: notifData.category,
- // Hack: Get the primary link
from the $content
- link: $content.find(
'.mw-echo-notification-primary-link' ).attr( 'href' )
- } ) );
- }
-
- widget.notifications.removeItems( [
widget.loadingOptionWidget ] );
- widget.notifications.addItems( optionWidgets );
- widget.popPending();
- widget.emit( 'update' );
-
- return optionWidgets;
- },
- // Failure
- function () {
- // TODO: Handle failures better
- widget.popPending();
- } );
- };
-
- /**
* Clear the notifications list. Leave only the dummy 'pending' option.
*/
mw.echo.ui.NotificationsWidget.prototype.clearNotifications = function
() {
- this.notifications.clearItems();
+ // this.notifications.clearItems();
// Add the dummy 'pending' option
- this.notifications.addItems( [ this.loadingOptionWidget ] );
- };
-
- /**
- * Get the notification widget type
- *
- * @return {string} Notification type; 'alert' or 'message'
- */
- mw.echo.ui.NotificationsWidget.prototype.getType = function () {
- return this.type;
- };
-
- /**
- * Set the notification widget type
- *
- * @param {string} type Notification type; 'alert' or 'message'
- */
- mw.echo.ui.NotificationsWidget.prototype.setType = function ( type ) {
- this.type = type;
+ // this.notifications.addItems( [ this.loadingOptionWidget ] );
};
/**
@@ -285,70 +191,34 @@
* remaining unread notifications when all notifications are marked
unread.
*/
mw.echo.ui.NotificationsWidget.prototype.markAllRead = function () {
- var widget = this,
- data = {
- action: 'echomarkread',
- uselang: this.userLang,
- sections: this.type
- };
+ // var widget = this,
+ // data = {
+ // action: 'echomarkread',
+ // uselang: this.userLang,
+ // sections: this.type
+ // };
- if ( !this.howManyUnread ) {
- return $.Deferred().resolve( 0 ).promise();
- }
+ // if ( !this.howManyUnread ) {
+ // return $.Deferred().resolve( 0 ).promise();
+ // }
- return this.api.postWithToken( 'edit', data )
- .then( function ( result ) {
- return
result.query.echomarkread[widget.type].rawcount || 0;
- } )
- .then( function ( readNotificationCount ) {
- var i, len,
- optionWidgets =
widget.notifications.getItems();
+ // return this.api.postWithToken( 'edit', data )
+ // .then( function ( result ) {
+ // return
result.query.echomarkread[widget.type].rawcount || 0;
+ // } )
+ // .then( function ( readNotificationCount ) {
+ // var i, len,
+ // optionWidgets =
widget.notifications.getItems();
- for ( i = 0, len = optionWidgets.length; i <
len; i++ ) {
- if ( optionWidgets[i].getData() ) {
- optionWidgets[i].toggleRead(
true );
- optionWidgets[i].toggleSeen(
true );
- }
- }
- widget.howManyUnread = readNotificationCount;
- return widget.howManyUnread;
- } );
- };
-
- /**
- * Get the seen timestamp in MW format.
- *
- * @return {number} Seen timestamp
- */
- mw.echo.ui.NotificationsWidget.prototype.getSeenTime = function () {
- return this.seenTime;
- };
-
- /**
- * Update the seen timestamp
- *
- * @return {jQuery.Promise} A promise that resolves with the seen
timestamp
- * @fires seen
- */
- mw.echo.ui.NotificationsWidget.prototype.updateSeenTime = function () {
- var widget = this;
-
- return this.api.post( {
- action: 'echomarkseen',
- token: mw.user.tokens.get( 'editToken' )
- } ).then( function ( data ) {
- var time = data.query.echomarkseen.timestamp;
-
- // update echo-seen-time value in JS (where it wouldn't
- // otherwise propagate until page reload)
- mw.user.options.set( 'echo-seen-time', time );
- widget.seenTime = time;
- widget.howManyUnseen = 0;
-
- widget.emit( 'seen', widget.seenTime );
-
- return widget.seenTime;
- } );
+ // for ( i = 0, len = optionWidgets.length; i <
len; i++ ) {
+ // if ( optionWidgets[i].getData() ) {
+ // optionWidgets[i].toggleRead(
true );
+ // optionWidgets[i].toggleSeen(
true );
+ // }
+ // }
+ // widget.howManyUnread = readNotificationCount;
+ // return widget.howManyUnread;
+ // } );
};
} )( mediaWiki, jQuery );
diff --git a/modules/viewmodel/mw.echo.dm.List.js
b/modules/viewmodel/mw.echo.dm.List.js
new file mode 100644
index 0000000..381ec5a
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.List.js
@@ -0,0 +1,272 @@
+( function ( $, mw ) {
+ /**
+ * Echo List mixin
+ *
+ * @mixin
+ * @abstract
+ * @constructor
+ * @param {Object} config Configuration options
+ */
+ mw.echo.dm.List = function mwFlowDmList( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ this.items = [];
+
+ // Store references to items by their ids
+ this.itemsById = {};
+
+ this.aggregateItemEvents = {};
+ };
+
+ /* Events */
+
+ /**
+ * @event add Items have been added
+ * @param {mw.echo.dm.NotificationItem[]} items Added items
+ * @param {number} index Index items were added at
+ */
+
+ /**
+ * @event remove Items have been removed
+ * @param {mw.echo.dm.NotificationItem[]} items Removed items
+ */
+
+ /**
+ * @event clear All items have been removed
+ */
+
+ /* Methods */
+
+ /**
+ * Get all items
+ *
+ * @return {mw.echo.dm.NotificationItem[]} Items in the list
+ */
+ mw.echo.dm.List.prototype.getItems = function () {
+ return this.items.slice( 0 );
+ };
+
+ /**
+ * Get an item by its id
+ * @param {string} id Item id
+ * @return {mw.echo.dm.NotificationItem} Item
+ */
+ mw.echo.dm.List.prototype.getItemById = function ( id ) {
+ return this.itemsById[ id ];
+ };
+
+ /**
+ * Get the index of a specific item
+ *
+ * @param {mw.echo.dm.NotificationItem} item Requested item
+ * @return {number} Index of the item
+ */
+ mw.echo.dm.List.prototype.getItemIndex = function ( item ) {
+ return this.items.indexOf( item );
+ };
+
+ /**
+ * Get number of items
+ *
+ * @return {number} Number of items in the list
+ */
+ mw.echo.dm.List.prototype.getItemCount = function () {
+ return this.items.length;
+ };
+
+ /**
+ * Check if a list contains no items.
+ *
+ * @return {boolean} Group is empty
+ */
+ mw.echo.dm.List.prototype.isEmpty = function () {
+ return !this.items.length;
+ };
+
+ /**
+ * Aggregate the events emitted by the group.
+ * Taken from oojs-ui's OO.ui.GroupElement#aggregate
+ *
+ * When events are aggregated, the group will listen to all contained
items for the event,
+ * and then emit the event under a new name. The new event will contain
an additional leading
+ * parameter containing the item that emitted the original event. Other
arguments emitted from
+ * the original event are passed through.
+ *
+ * @param {Object.<string,string|null>} events An object keyed by the
name of the event that should be
+ * aggregated (e.g., ‘click’) and the value of the new name to use
(e.g., ‘groupClick’).
+ * A `null` value will remove aggregated events.
+
+ * @throws {Error} An error is thrown if aggregation already exists.
+ */
+ mw.echo.dm.List.prototype.aggregate = function ( events ) {
+ var i, len, item, add, remove, itemEvent, groupEvent;
+
+ for ( itemEvent in events ) {
+ groupEvent = events[ itemEvent ];
+
+ // Remove existing aggregated event
+ if ( Object.prototype.hasOwnProperty.call(
this.aggregateItemEvents, itemEvent ) ) {
+ // Don't allow duplicate aggregations
+ if ( groupEvent ) {
+ throw new Error( 'Duplicate item event
aggregation for ' + itemEvent );
+ }
+ // Remove event aggregation from existing items
+ for ( i = 0, len = this.items.length; i < len;
i++ ) {
+ item = this.items[ i ];
+ if ( item.connect && item.disconnect ) {
+ remove = {};
+ remove[ itemEvent ] = [ 'emit',
this.aggregateItemEvents[ itemEvent ], item ];
+ item.disconnect( this, remove );
+ }
+ }
+ // Prevent future items from aggregating event
+ delete this.aggregateItemEvents[ itemEvent ];
+ }
+
+ // Add new aggregate event
+ if ( groupEvent ) {
+ // Make future items aggregate event
+ this.aggregateItemEvents[ itemEvent ] =
groupEvent;
+ // Add event aggregation to existing items
+ for ( i = 0, len = this.items.length; i < len;
i++ ) {
+ item = this.items[ i ];
+ if ( item.connect && item.disconnect ) {
+ add = {};
+ add[ itemEvent ] = [ 'emit',
groupEvent, item ];
+ item.connect( this, add );
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Add items
+ *
+ * @param {mw.echo.dm.NotificationItem[]} items Items to add
+ * @param {number} index Index to add items at
+ * @chainable
+ * @fires add
+ */
+ mw.echo.dm.List.prototype.addItems = function ( items, index ) {
+ var i, len, item, event, events, currentIndex, existingItem, at;
+
+ if ( items.length === 0 ) {
+ return this;
+ }
+
+ // Support adding existing items at new locations
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ item = items[i];
+ existingItem = this.getItemById( item.getId() );
+
+ // Check if item exists then remove it first,
effectively "moving" it
+ currentIndex = this.items.indexOf( existingItem );
+ if ( currentIndex >= 0 ) {
+ this.removeItems( [ existingItem ] );
+ // Adjust index to compensate for removal
+ if ( currentIndex < index ) {
+ index--;
+ }
+ }
+
+ // Add the item
+ if ( item.connect && item.disconnect &&
!$.isEmptyObject( this.aggregateItemEvents ) ) {
+ events = {};
+ for ( event in this.aggregateItemEvents ) {
+ events[ event ] = [ 'emit',
this.aggregateItemEvents[ event ], item ];
+ }
+ item.connect( this, events );
+ }
+
+ // Add by reference
+ this.itemsById[ item.getId() ] = items[i];
+ }
+
+ if ( index === undefined || index < 0 || index >=
this.items.length ) {
+ at = this.items.length;
+ this.items.push.apply( this.items, items );
+ } else if ( index === 0 ) {
+ at = 0;
+ this.items.unshift.apply( this.items, items );
+ } else {
+ at = index;
+ this.items.splice.apply( this.items, [ index, 0
].concat( items ) );
+ }
+ this.emit( 'add', items, at );
+
+ return this;
+ };
+
+ /**
+ * Remove items
+ *
+ * @param {mw.echo.dm.NotificationItem[]} items Items to remove
+ * @chainable
+ * @fires remove
+ */
+ mw.echo.dm.List.prototype.removeItems = function ( items ) {
+ var i, len, item, index, remove, itemEvent,
+ removed = [];
+
+ if ( items.length === 0 ) {
+ return this;
+ }
+
+ // Remove specific items
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ item = items[ i ];
+ index = this.items.indexOf( item );
+ if ( index !== -1 ) {
+ if (
+ item.connect && item.disconnect &&
+ !$.isEmptyObject(
this.aggregateItemEvents )
+ ) {
+ remove = {};
+ if (
Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
+ remove[ itemEvent ] = [ 'emit',
this.aggregateItemEvents[ itemEvent ], item ];
+ }
+ item.disconnect( this, remove );
+ }
+ this.items.splice( index, 1 );
+ // Remove reference by Id
+ delete this.itemsById[ item.getId() ];
+ }
+ }
+ this.emit( 'remove', removed );
+
+ return this;
+ };
+
+ /**
+ * Clear all items
+ *
+ * @fires clear
+ */
+ mw.echo.dm.List.prototype.clearItems = function () {
+ var i, len, item, remove, itemEvent;
+
+ // Remove all items
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if (
+ item.connect && item.disconnect &&
+ !$.isEmptyObject( this.aggregateItemEvents )
+ ) {
+ remove = {};
+ if ( Object.prototype.hasOwnProperty.call(
this.aggregateItemEvents, itemEvent ) ) {
+ remove[ itemEvent ] = [ 'emit',
this.aggregateItemEvents[ itemEvent ], item ];
+ }
+ item.disconnect( this, remove );
+ }
+ }
+
+ this.items = [];
+ this.itemsById = {};
+
+ this.emit( 'clear' );
+
+ return this;
+ };
+}( jQuery, mediaWiki ) );
diff --git a/modules/viewmodel/mw.echo.dm.NotificationItem.js
b/modules/viewmodel/mw.echo.dm.NotificationItem.js
new file mode 100644
index 0000000..b48089c
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.NotificationItem.js
@@ -0,0 +1,172 @@
+( function ( mw, $ ) {
+ /**
+ * Echo notification NotificationItem model
+ *
+ * @abstract
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {number} id Notification id,
+ * @param {Object} [config] Configuration object
+ * @cfg {jQuery|string} [content] The html content of this notification
+ * @cfg {string} [category] The category of this notification. The
category identifies
+ * where the notification originates from.
+ * @cfg {boolean} [read=false] State the read state of the option
+ * @cfg {boolean} [seen=false] State the seen state of the option
+ * @cfg {string} [timstamp] Notification timestamp in Mediawiki
timestamp format
+ */
+ mw.echo.dm.NotificationItem = function mwFlowDmNotificationItem( id,
config ) {
+ // Mixin constructor
+ OO.EventEmitter.call( this );
+
+ this.id = id || null;
+
+ // TODO: We should work on the API to release and work with
actual
+ // data here, rather than getting a pre-made html content of the
+ // notification.
+ this.content = config.content || $();
+
+ this.category = config.category || '';
+
+ this.toggleRead( !!config.read );
+ this.toggleSeen( !!config.seen );
+
+ this.setTimestamp( Number( config.timestamp ) || ( Date.now() /
1000 ) );
+ this.setPrimaryLink( config.primaryLink );
+ };
+
+ /* Inheritance */
+
+ OO.mixinClass( mw.echo.dm.NotificationItem, OO.EventEmitter );
+
+ /* Events */
+
+ /**
+ * @event seen
+ * @param {boolean} [seen] Notification is seen
+ *
+ * Seen status of the notification has changed
+ */
+
+ /**
+ * @event read
+ * @param {boolean} [read] Notification is read
+ *
+ * Read status of the notification has changed
+ */
+
+ /* Methods */
+
+ /**
+ * Get NotificationItem id
+ * @return {string} NotificationItem Id
+ */
+ mw.echo.dm.NotificationItem.prototype.getId = function () {
+ return this.id;
+ };
+
+ /**
+ * Set NotificationItem id
+ * @param {string} id NotificationItem Id
+ */
+ mw.echo.dm.NotificationItem.prototype.setId = function ( id ) {
+ this.id = id;
+ };
+
+ /**
+ * Get NotificationItem content
+ * @return {jQuery|string} NotificationItem content
+ */
+ mw.echo.dm.NotificationItem.prototype.getContent = function () {
+ return this.content;
+ };
+
+ /**
+ * Get NotificationItem category
+ * @return {string} NotificationItem category
+ */
+ mw.echo.dm.NotificationItem.prototype.getCategory = function () {
+ return this.category;
+ };
+
+ /**
+ * Check whether this notification item is read
+ * @return {boolean} Notification item is read
+ */
+ mw.echo.dm.NotificationItem.prototype.isRead = function () {
+ return this.read;
+ };
+
+ /**
+ * Check whether this notification item is seen
+ * @return {boolean} Notification item is seen
+ */
+ mw.echo.dm.NotificationItem.prototype.isSeen = function () {
+ return this.seen;
+ };
+
+ /**
+ * Toggle the read state of the widget
+ *
+ * @param {boolean} [read] The current read state. If not given, the
state will
+ * become the opposite of its current state.
+ */
+ mw.echo.dm.NotificationItem.prototype.toggleRead = function ( read ) {
+ read = read !== undefined ? read : !this.read;
+ if ( this.read !== read ) {
+ this.read = read;
+ this.emit( 'read', this.read );
+ }
+ };
+
+ /**
+ * Toggle the seen state of the widget
+ *
+ * @param {boolean} [seen] The current seen state. If not given, the
state will
+ * become the opposite of its current state.
+ */
+ mw.echo.dm.NotificationItem.prototype.toggleSeen = function ( seen ) {
+ seen = seen !== undefined ? seen : !this.seen;
+ if ( this.seen !== seen ) {
+ this.seen = seen;
+ this.emit( 'seen', this.seen );
+ }
+ };
+
+ /**
+ * Set the notification timestamp
+ *
+ * @param {number} timestamp Notification timestamp
+ */
+ mw.echo.dm.NotificationItem.prototype.setTimestamp = function (
timestamp ) {
+ this.timestamp = Number( timestamp );
+ };
+
+ /**
+ * Get the notification timestamp
+ *
+ * @return {number} Notification timestamp
+ */
+ mw.echo.dm.NotificationItem.prototype.getTimestamp = function () {
+ return this.timestamp;
+ };
+
+ /**
+ * Set the notification link
+ *
+ * @param {string} link Notification link
+ */
+ mw.echo.dm.NotificationItem.prototype.setPrimaryLink = function ( link
) {
+ this.primaryLink = link;
+ };
+
+ /**
+ * Get the notification link
+ *
+ * @return {string} Notification link
+ */
+ mw.echo.dm.NotificationItem.prototype.getPrimaryLink = function () {
+ return this.primaryLink;
+ };
+
+}( mediaWiki, jQuery ) );
diff --git a/modules/viewmodel/mw.echo.dm.NotificationList.js
b/modules/viewmodel/mw.echo.dm.NotificationList.js
new file mode 100644
index 0000000..7d4fbea
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.NotificationList.js
@@ -0,0 +1,26 @@
+( function ( mw ) {
+ /**
+ * Notification list
+ *
+ * @class
+ * @mixins OO.EventEmitter
+ * @mixins mw.echo.dm.List
+ *
+ * @constructor
+ * @param {Object} [config] Configuration object
+ */
+ mw.echo.dm.NotificationList = function MwEchoDmNotificationList() {
+
+ // Mixin constructor
+ OO.EventEmitter.call( this );
+
+ // Mixin constructor
+ mw.echo.dm.List.call( this );
+ };
+
+ /* Initialization */
+
+ OO.initClass( mw.echo.dm.NotificationList );
+ OO.mixinClass( mw.echo.dm.NotificationList, OO.EventEmitter );
+ OO.mixinClass( mw.echo.dm.NotificationList, mw.echo.dm.List );
+} )( mediaWiki );
diff --git a/modules/viewmodel/mw.echo.dm.NotificationsModel.js
b/modules/viewmodel/mw.echo.dm.NotificationsModel.js
new file mode 100644
index 0000000..9401fd2
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.NotificationsModel.js
@@ -0,0 +1,296 @@
+( function ( mw, $ ) {
+ /**
+ * Notification view model
+ *
+ * @class
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration object
+ * @cfg {string} [type='alert'] Notification type 'alert', 'message' or
'all'
+ * @cfg {number} [limit=25] Notification limit
+ * @cfg {string} [userLang] User language
+ */
+ mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel(
config ) {
+ config = config || {};
+
+ // Mixin constructor
+ OO.EventEmitter.call( this );
+
+ // Mixin constructor
+ mw.echo.dm.List.call( this );
+
+ this.type = config.type || 'alert';
+ this.limit = config.limit || 25;
+ this.userLang = config.userLang || 'en';
+
+ this.api = new mw.Api( { ajax: { cache: false } } );
+
+ this.seenTime = mw.user.options.get( 'echo-seen-time' );
+
+ // Store references to unseen and unread notifications
+ this.unseenNotifications = new mw.echo.dm.NotificationList();
+ this.unreadNotifications = new mw.echo.dm.NotificationList();
+
+ // Events
+ this.aggregate( {
+ seen: 'itemSeen',
+ read: 'itemRead'
+ } );
+
+ this.connect( this, {
+ itemSeen: 'onItemSeen',
+ itemRead: 'onItemRead'
+ } );
+ };
+
+ /* Initialization */
+
+ OO.initClass( mw.echo.dm.NotificationsModel );
+ OO.mixinClass( mw.echo.dm.NotificationsModel, OO.EventEmitter );
+ OO.mixinClass( mw.echo.dm.NotificationsModel, mw.echo.dm.List );
+
+ /* Events */
+
+ /**
+ * @event updateSeenTime
+ * @param {number} seenTime Seen time
+ *
+ * Seen time has been updated
+ */
+
+ /* Methods */
+
+ /**
+ * Respond to item seen state change
+ *
+ * @param {mw.echo.dm.NotificationItem} item Notification item
+ * @param {boolean} isSeen Notification is seen
+ * @fires unseenChange
+ */
+ mw.echo.dm.NotificationsModel.prototype.onItemSeen = function ( item,
isSeen ) {
+ var id = item && item.getId(),
+ unseenItem = id &&
this.unseenNotifications.getItemById( id );
+
+ if ( unseenItem ) {
+ if ( isSeen ) {
+ this.unseenNotifications.removeItems( [
unseenItem ] );
+ } else {
+ this.unseenNotifications.addItems( [ unseenItem
] );
+ }
+ this.emit( 'unseenChange',
this.unseenNotifications.getItems() );
+ }
+ };
+
+ /**
+ * Respond to item read state change
+ *
+ * @param {mw.echo.dm.NotificationItem} item Notification item
+ * @param {boolean} isRead Notification is read
+ * @fires notificationRead
+ */
+ mw.echo.dm.NotificationsModel.prototype.onItemRead = function ( item,
isRead ) {
+ var id = item && item.getId(),
+ unreadItem = id &&
this.unreadNotifications.getItemById( id );
+
+ if ( unreadItem ) {
+ if ( isRead ) {
+ this.unseenNotifications.removeItems( [
unreadItem ] );
+ } else {
+ this.unseenNotifications.addItems( [ unreadItem
] );
+ }
+ this.emit( 'unreadChange',
this.unreadNotifications.getItems() );
+ }
+ };
+
+ /**
+ * Get the type of the notifications that this model deals with.
+ * Notifications type are given from the API: 'alert', 'message', 'all'
+ *
+ * @return {string} Notifications type
+ */
+ mw.echo.dm.NotificationsModel.prototype.getType = function () {
+ return this.type;
+ };
+
+ /**
+ * Get the counter of how many notifications are unseen
+ *
+ * @return {number} Number of unseen notifications
+ */
+ mw.echo.dm.NotificationsModel.prototype.getUnseenCount = function () {
+ return this.unseenNotifications.getItemCount();
+ };
+
+ /**
+ * Get the counter of how many notifications are unread
+ *
+ * @return {number} Number of unread notifications
+ */
+ mw.echo.dm.NotificationsModel.prototype.getUnreadCount = function () {
+ return this.unreadNotifications.getItemCount();
+ };
+
+ /**
+ * Set the system seen time - the last time we've marked notification
as seen
+ *
+ * @param {string|number} Mediawiki seen timestamp
+ */
+ mw.echo.dm.NotificationsModel.prototype.setSeenTime = function ( time )
{
+ this.seenTime = Number( time );
+ };
+
+ /**
+ * Get the system seen time
+ *
+ * @return {number} Mediawiki seen timestamp
+ */
+ mw.echo.dm.NotificationsModel.prototype.getSeenTime = function () {
+ return this.seenTime;
+ };
+
+ /**
+ * Update the seen timestamp
+ *
+ * @return {jQuery.Promise} A promise that resolves with the seen
timestamp
+ * @fires seen
+ */
+ mw.echo.dm.NotificationsModel.prototype.updateSeenTime = function () {
+ var model = this;
+
+ return this.api.post( {
+ action: 'echomarkseen',
+ token: mw.user.tokens.get( 'editToken' )
+ } ).then( function ( data ) {
+ var time = data.query.echomarkseen.timestamp;
+
+ // update echo-seen-time value in JS (where it wouldn't
+ // otherwise propagate until page reload)
+ mw.user.options.set( 'echo-seen-time', time );
+ model.setSeenTime( time );
+ // model.unseenCounter = 0;
+ model.emit( 'updateSeenTime' );
+ } );
+ };
+
+ /**
+ * Mark all notifications as read
+ *
+ * @return {jQuery.Promise} A promise that resolves when all
notifications
+ * were marked as read.
+ */
+ mw.echo.dm.NotificationsModel.prototype.markAllRead = function () {
+ var model = this,
+ data = {
+ action: 'echomarkread',
+ uselang: this.userLang,
+ sections: this.type
+ };
+
+ if ( !this.unreadNotifications.getItemCount() ) {
+ return $.Deferred().resolve( 0 ).promise();
+ }
+
+ return this.api.postWithToken( 'edit', data )
+ .then( function ( result ) {
+ return
result.query.echomarkread[model.type].rawcount || 0;
+ } )
+ .then( function () {
+ var i, len,
+ items =
model.unreadNotifications.getItems();
+
+ for ( i = 0, len = items.length; i < len; i++ )
{
+ items[i].toggleRead( true );
+ items[i].toggleSeen( true );
+ }
+ model.unreadNotifications.clearItems();
+ } );
+ };
+
+ /**
+ * Fetch notifications from the API and update the notifications list.
+ *
+ * @return {jQuery.Promise} A promise that resolves with an array of
notification
+ * id's.
+ */
+ mw.echo.dm.NotificationsModel.prototype.fetchNotifications = function
() {
+ var model = this,
+ apiData = {
+ action: 'query',
+ meta: 'notifications',
+ notsections: this.type,
+ notmessageunreadfirst: 1,
+ notformat: 'flyout',
+ notlimit: this.limit,
+ notprop: 'index|list|count',
+ uselang: this.userLang
+ };
+
+ return this.api.get( apiData )
+ .then( function ( result ) {
+ var notifData, i, len, $content, wasSeen,
wasRead, notificationModel,
+ optionItems = [],
+ idArray = [],
+ data = result.query.notifications;
+
+ for ( i = 0, len = data.index.length; i < len;
i++ ) {
+ notifData = data.list[ data.index[i] ];
+ // TODO: This should really be
formatted better, and the OptionWidget
+ // should be the one that displays
whatever icon relates to this notification
+ // according to its type.
+ $content = $( $.parseHTML(
notifData['*'] ) );
+
+ wasRead = !!notifData.read;
+ wasSeen = notifData.timestamp.mw <=
model.getSeenTime();
+ notificationModel = new
mw.echo.dm.NotificationItem(
+ notifData.id,
+ {
+ read: wasRead,
+ seen: wasSeen,
+ timestamp:
notifData.timestamp.mw,
+ category:
notifData.category,
+ content: $content,
+ // Hack: Get the
primary link from the $content
+ primaryLink:
$content.find( '.mw-echo-notification-primary-link' ).attr( 'href' )
+ }
+ );
+
+ idArray.push( notifData.id );
+ optionItems.push( notificationModel );
+
+ if ( !wasRead ) {
+
model.unreadNotifications.addItems( [ notificationModel ] );
+ }
+ if ( !wasSeen ) {
+
model.unseenNotifications.addItems( [ notificationModel ] );
+ }
+ }
+ model.addItems( optionItems );
+
+ return idArray;
+ } );
+ };
+
+ /**
+ * 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 () {
+ var apiData = {
+ action: 'query',
+ meta: 'notifications',
+ notsections: this.getType(),
+ 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 );
diff --git a/modules/viewmodel/mw.echo.dm.js b/modules/viewmodel/mw.echo.dm.js
new file mode 100644
index 0000000..e7d7c70
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.js
@@ -0,0 +1,4 @@
+( function ( mw ) {
+ mw.echo = mw.echo || {};
+ mw.echo.dm = {};
+} )( mediaWiki );
--
To view, visit https://gerrit.wikimedia.org/r/234451
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia3b220b373b70b1419fecd6d0edb1d7df1af4417
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits