Merge "Generalize the SubGroupListWidget"

This commit is contained in:
jenkins-bot 2016-05-26 20:26:31 +00:00 committed by Gerrit Code Review
commit 35f1388e7f
6 changed files with 186 additions and 31 deletions

View file

@ -202,7 +202,7 @@
items.push(
new mw.echo.dm.NotificationItem( groupItems[ i ].id, $.extend( notifData, {
source: group,
bundle: true,
bundled: true,
foreign: true
} ) )
);
@ -293,7 +293,7 @@
sourceModel = xwikiModel.getList().getGroupBySource( modelSource );
notifs = sourceModel.findByIds( itemIds );
sourceModel.removeItems( notifs );
sourceModel.discardItems( notifs );
return this.api.markItemsRead( itemIds, modelSource, true )
.then( this.refreshUnreadCount.bind( this ) );

View file

@ -23,6 +23,7 @@
* @cfg {string} [timestamp] Notification timestamp in Mediawiki timestamp format
* @cfg {string} [primaryUrl] Notification primary link in raw url format
* @cfg {boolean} [foreign=false] This notification is from a foreign source
* @cfg {boolean} [bundled=false] This notification is part of a bundle
* @cfg {string} [source] The source this notification is coming from, if it is foreign
* @cfg {Object[]} [secondaryUrls] An array of objects defining the secondary URLs
* for this notification. The secondary URLs are expected to have this structure:
@ -58,6 +59,7 @@
this.category = config.category || '';
this.type = config.type || 'message';
this.foreign = !!config.foreign;
this.bundled = !!config.bundled;
this.source = config.source || '';
this.iconType = config.iconType;
this.iconURL = config.iconURL;
@ -157,6 +159,15 @@
return this.foreign;
};
/**
* Check whether this notification item is part of a bundle
*
* @return {boolean} Notification item is part of a bundle
*/
mw.echo.dm.NotificationItem.prototype.isBundled = function () {
return this.bundled;
};
/**
* Set this notification item as foreign
*

View file

@ -72,7 +72,7 @@
/**
* Set the items in this list
*
* @param {mw.echo.dm.NotificationItem} items Items to insert into the list
* @param {mw.echo.dm.NotificationItem[]} items Items to insert into the list
* @fires update
*/
mw.echo.dm.NotificationsList.prototype.setItems = function ( items ) {
@ -81,6 +81,22 @@
this.emit( 'update', this.getItems() );
};
/**
* Discard items from the list.
*
* This is a more precise operation than 'removeItems' because when
* the list is resorting the position of a single item, it removes
* the item and reinserts it, which makes the 'remove' event unhelpful
* to differentiate between actually discarding items, and only
* temporarily moving them.
*
* @param {mw.echo.dm.NotificationItem[]} items Items to insert into the list
*/
mw.echo.dm.NotificationsList.prototype.discardItems = function ( items ) {
this.removeItems( items );
this.emit( 'discard', items );
};
/**
* Get an array of all items' IDs.
*
@ -176,4 +192,5 @@
mw.echo.dm.NotificationsList.prototype.isGroup = function () {
return false;
};
} )( mediaWiki );

View file

@ -37,4 +37,15 @@
border-bottom: 1px #dddddd solid;
margin-bottom: 0.4em;
}
.mw-echo-ui-subGroupListWidget-header {
margin-bottom: @bundle-group-padding;
&-row-title {
// Override OOUI's line height for labels
line-height: 1em !important;
font-weight: bold;
color: #666666;
}
}
}

View file

@ -6,12 +6,25 @@
padding-top: @bundle-group-padding;
}
&-header {
display: table;
width: 100%;
&-row {
display: table-row;
&-title {
// Override OOUI's line height for labels
line-height: 1em !important;
font-weight: bold;
color: #666666;
margin-bottom: @bundle-group-padding;
display: table-cell;
width: 100%;
vertical-align: bottom;
}
&-markAllReadButton {
display: table-cell;
text-align: right;
padding-bottom: 0.5em;
}
}
}
.mw-echo-ui-sortedListWidget {

View file

@ -8,11 +8,14 @@
* @param {mw.echo.dm.SortedList} listModel Notifications list model for this source
* @param {Object} config Configuration object
* @cfg {boolean} [showTitle=false] Show the title of this group
* @cfg {boolean} [showMarkAllRead=false] Show a mark all read button for this group
* @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
* for popups.
*/
mw.echo.ui.SubGroupListWidget = function MwEchoUiSubGroupListWidget( controller, listModel, config ) {
var sourceURL;
var sourceURL,
$header = $( '<div>' )
.addClass( 'mw-echo-ui-subGroupListWidget-header' );
config = config || {};
@ -23,6 +26,7 @@
mw.echo.ui.SubGroupListWidget.parent.call( this, $.extend( { data: this.getSource() }, config ) );
this.showTitle = !!config.showTitle;
this.showMarkAllRead = !!config.showMarkAllRead;
this.$overlay = config.$overlay || this.$element;
this.listWidget = new mw.echo.ui.SortedListWidget(
@ -45,28 +49,60 @@
sourceURL = this.model.getSourceURL() ?
this.model.getSourceURL().replace( '$1', 'Special:Notifications' ) :
null;
if ( sourceURL ) {
this.title = new OO.ui.ButtonWidget( {
framed: false,
classes: [ 'mw-echo-ui-subGroupListWidget-title' ],
classes: [ 'mw-echo-ui-subGroupListWidget-header-row-title' ],
href: sourceURL
} );
} else {
this.title = new OO.ui.LabelWidget( {
classes: [ 'mw-echo-ui-subGroupListWidget-header-row-title' ]
} );
}
if ( this.model.getTitle() ) {
this.title.setLabel( this.model.getTitle() );
}
this.title.toggle( this.showTitle );
// Events
this.model.connect( this, {
// We really only need to listen to 'remove' item here
// There is no other update event worthwhile in this list.
remove: 'onModelRemoveItem',
update: 'onModelUpdate' // Adding all items
// Mark all as read button
this.markAllReadButton = new OO.ui.ButtonWidget( {
framed: true,
label: mw.msg( 'echo-mark-all-as-read' ),
classes: [ 'mw-echo-ui-subGroupListWidget-header-row-markAllReadButton' ]
} );
// Events
this.model.connect( this, {
// Cross-wiki items can be discarded when marked as read.
// We need to differentiate this explicit action from the
// action of 'remove' because 'remove' is also used when
// an item is resorted by OO.SortedEmitterWidget before
// it is re-added again
discard: 'onModelDiscardItems',
// Update all items
update: 'resetItemsFromModel'
} );
this.markAllReadButton.connect( this, { click: 'onMarkAllReadButtonClick' } );
// We must aggregate on item update, so we know when and if all
// items are read and can hide/show the 'mark all read' button
this.model.aggregate( { update: 'itemUpdate' } );
this.model.connect( this, { itemUpdate: 'toggleMarkAllReadButton' } );
// Initialize
this.toggleMarkAllReadButton();
this.$element
.addClass( 'mw-echo-ui-subGroupListWidget' )
.append(
$header.append(
$( '<div>' )
.addClass( 'mw-echo-ui-subGroupListWidget-header-row' )
.append(
this.title.$element,
this.markAllReadButton.$element
)
),
this.listWidget.$element
);
};
@ -78,14 +114,46 @@
/* Methods */
/**
* Respond to model update event
*
* @param {mw.echo.dm.NotificationItem[]} items Item models that are added
* Toggle the visibility of the mark all read button for this group
* based on whether there are unread notifications
*/
mw.echo.ui.SubGroupListWidget.prototype.onModelUpdate = function ( items ) {
mw.echo.ui.SubGroupListWidget.prototype.toggleMarkAllReadButton = function () {
this.markAllReadButton.toggle( this.hasUnread() );
};
/**
* Respond to 'mark all as read' button click
*/
mw.echo.ui.SubGroupListWidget.prototype.onMarkAllReadButtonClick = function () {
this.controller.markEntireListModelRead( this.model.getSource() );
};
/**
* Check whether this sub group list has any unread notifications
*
* @return {boolean} Sub group has unread notifications
*/
mw.echo.ui.SubGroupListWidget.prototype.hasUnread = function () {
var isUnread = function ( item ) {
return !item.isRead();
},
items = this.model.getItems();
return items.some( isUnread );
};
/**
* Reset the items and rebuild them according to the model.
*
* @param {mw.echo.dm.NotificationItem[]} [items] Item models that are added.
* If this is empty, the widget will request all the items from the model.
*/
mw.echo.ui.SubGroupListWidget.prototype.resetItemsFromModel = function ( items ) {
var i,
itemWidgets = [];
items = items || this.model.getItems();
for ( i = 0; i < items.length; i++ ) {
itemWidgets.push(
new mw.echo.ui.SingleNotificationItemWidget(
@ -93,7 +161,7 @@
items[ i ],
{
$overlay: this.$overlay,
bundle: true
bundle: items[ i ].isBundled()
}
)
);
@ -106,13 +174,19 @@
};
/**
* Respond to mode remove event. This may happen when an item
* Respond to model remove event. This may happen when an item
* is marked as read.
*
* @param {mw.echo.dm.NotificationItem} item Notification item model
* @param {mw.echo.dm.NotificationItem[]} items Notification item models
*/
mw.echo.ui.SubGroupListWidget.prototype.onModelRemoveItem = function ( item ) {
this.listWidget.removeItems( [ this.listWidget.getItemFromId( item.getId() ) ] );
mw.echo.ui.SubGroupListWidget.prototype.onModelDiscardItems = function ( items ) {
var i,
itemWidgets = [];
for ( i = 0; i < items.length; i++ ) {
itemWidgets.push( this.listWidget.getItemFromId( items[ i ].getId() ) );
}
this.listWidget.removeItems( itemWidgets );
};
/**
@ -157,6 +231,35 @@
return this.model.getSource();
};
/**
* Get an array of IDs of all of the items in this group
*
* @return {number[]} Array of item IDs
*/
mw.echo.ui.SubGroupListWidget.prototype.getAllItemIDs = function () {
return this.model.getAllItemIds();
};
/**
* Get an array of IDs of all of the items in this group that
* correspond to a specific type
*
* @param {string} type Item type
* @return {number[]} Array of item IDs
*/
mw.echo.ui.SubGroupListWidget.prototype.getAllItemIDsByType = function ( type ) {
return this.model.getAllItemIdsByType( type );
};
/**
* Check whether this group is foreign
*
* @return {boolean} This group is foreign
*/
mw.echo.ui.SubGroupListWidget.prototype.isForeign = function () {
return this.model.isForeign();
};
/**
* Get the group id, which is represented by its source.
* This is meant for sorting callbacks that fallback on