mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-28 01:30:15 +00:00
Merge "Generalize the SubGroupListWidget"
This commit is contained in:
commit
35f1388e7f
|
@ -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 ) );
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue