mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-27 17:20:40 +00:00
47ea647e45
Change-Id: I1e603647c161bf940690f85f676edd7ebde7917d
316 lines
8.8 KiB
JavaScript
316 lines
8.8 KiB
JavaScript
( function () {
|
|
/**
|
|
* Sub group list widget.
|
|
* This widget contains a list of notifications from a single source
|
|
* in a cross-wiki notifications group.
|
|
*
|
|
* @param {mw.echo.Controller} controller Notifications controller
|
|
* @param {mw.echo.dm.SortedList} listModel Notifications list model for this source
|
|
* @param {Object} config Configuration object
|
|
* @param {boolean} [config.showTitle=false] Show the title of this group
|
|
* @param {boolean} [config.showMarkAllRead=false] Show a mark all read button for this group
|
|
* @param {boolean} [config.animateSorting=false] Animate the sorting of items
|
|
* @param {jQuery} [config.$overlay] A jQuery element functioning as an overlay
|
|
* for popups.
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget = function MwEchoUiSubGroupListWidget( controller, listModel, config ) {
|
|
this.$header = $( '<div>' )
|
|
.addClass( 'mw-echo-ui-subGroupListWidget-header' );
|
|
|
|
config = config || {};
|
|
|
|
this.controller = controller;
|
|
this.model = listModel;
|
|
|
|
// Parent constructor
|
|
mw.echo.ui.SubGroupListWidget.super.call( this, Object.assign( { 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(
|
|
// Sorting callback
|
|
config.sortingCallback || ( ( a, b ) => {
|
|
// Reverse sorting
|
|
if ( b.getTimestamp() < a.getTimestamp() ) {
|
|
return -1;
|
|
} else if ( b.getTimestamp() > a.getTimestamp() ) {
|
|
return 1;
|
|
}
|
|
|
|
// Fallback on IDs
|
|
return b.getId() - a.getId();
|
|
} ),
|
|
// Config
|
|
{
|
|
$overlay: this.$overlay,
|
|
animated: config.animateSorting
|
|
}
|
|
);
|
|
|
|
const 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-header-row-title',
|
|
'mw-echo-ui-subGroupListWidget-header-row-cell'
|
|
],
|
|
href: sourceURL
|
|
} );
|
|
} else {
|
|
this.title = new OO.ui.LabelWidget( {
|
|
classes: [
|
|
'mw-echo-ui-subGroupListWidget-header-row-title',
|
|
'mw-echo-ui-subGroupListWidget-header-row-cell'
|
|
]
|
|
} );
|
|
}
|
|
|
|
if ( this.model.getTitle() ) {
|
|
this.title.setLabel( this.model.getTitle() );
|
|
}
|
|
this.title.toggle( this.showTitle );
|
|
|
|
// Mark all as read button
|
|
this.markAllReadButton = new OO.ui.ButtonWidget( {
|
|
framed: true,
|
|
icon: 'checkAll',
|
|
label: mw.msg( 'echo-specialpage-section-markread' ),
|
|
classes: [
|
|
'mw-echo-ui-subGroupListWidget-header-row-markAllReadButton',
|
|
'mw-echo-ui-subGroupListWidget-header-row-cell'
|
|
]
|
|
} );
|
|
|
|
// 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.model.connect( this, { itemUpdate: 'toggleMarkAllReadButton' } );
|
|
this.markAllReadButton.connect( this, { click: 'onMarkAllReadButtonClick' } );
|
|
|
|
// Initialize
|
|
this.toggleMarkAllReadButton();
|
|
|
|
this.$element
|
|
.addClass( 'mw-echo-ui-subGroupListWidget' )
|
|
.append(
|
|
this.$header.append(
|
|
$( '<div>' )
|
|
.addClass( 'mw-echo-ui-subGroupListWidget-header-row' )
|
|
.append(
|
|
this.title.$element,
|
|
this.markAllReadButton.$element
|
|
)
|
|
),
|
|
this.listWidget.$element
|
|
);
|
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
|
this.$pageContentText = $( '#mw-content-text' );
|
|
$( window ).on( 'resize', this.resizeHeader.bind( this ) );
|
|
|
|
// Resize the header after the stack finishes loading
|
|
// so the widget is attached
|
|
setTimeout( this.resizeHeader.bind( this ), 0 );
|
|
};
|
|
|
|
/* Initialization */
|
|
|
|
OO.inheritClass( mw.echo.ui.SubGroupListWidget, OO.ui.Widget );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Respond to window resize event
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.resizeHeader = function () {
|
|
const contentWidth = this.$pageContentText.width(),
|
|
screenTooNarrow = this.$header.width() > contentWidth;
|
|
|
|
// Screen too narrow, put the button under the date
|
|
this.title.$element.toggleClass(
|
|
'mw-echo-ui-subGroupListWidget-header-row-cell',
|
|
!screenTooNarrow
|
|
);
|
|
this.markAllReadButton.$element.toggleClass(
|
|
'mw-echo-ui-subGroupListWidget-header-row-cell',
|
|
!screenTooNarrow
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Destroy the widget and disconnect events
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.destroy = function () {
|
|
$( window ).off( 'resize' );
|
|
};
|
|
|
|
/**
|
|
* Toggle the visibility of the mark all read button for this group
|
|
* based on whether there are unread notifications
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.toggleMarkAllReadButton = function () {
|
|
this.markAllReadButton.toggle( this.showMarkAllRead && this.hasUnread() );
|
|
};
|
|
|
|
/**
|
|
* Respond to 'mark all as read' button click
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.onMarkAllReadButtonClick = function () {
|
|
this.controller.markEntireListModelRead( this.model.getName() );
|
|
};
|
|
|
|
/**
|
|
* Check whether this sub group list has any unread notifications
|
|
*
|
|
* @return {boolean} Sub group has unread notifications
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.hasUnread = function () {
|
|
return this.model.getItems().some( ( item ) => !item.isRead() );
|
|
};
|
|
|
|
/**
|
|
* 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 ) {
|
|
const itemWidgets = [];
|
|
let $elements = $();
|
|
|
|
items = items || this.model.getItems();
|
|
|
|
for ( let i = 0; i < items.length; i++ ) {
|
|
const widget = new mw.echo.ui.SingleNotificationItemWidget(
|
|
this.controller,
|
|
items[ i ],
|
|
{
|
|
$overlay: this.$overlay,
|
|
bundle: items[ i ].isBundled()
|
|
}
|
|
);
|
|
itemWidgets.push( widget );
|
|
$elements = $elements.add( widget.$element );
|
|
}
|
|
|
|
// Clear the current items if any exist
|
|
this.getListWidget().clearItems();
|
|
|
|
// fire render hook
|
|
mw.hook( 'ext.echo.notifications.beforeRender' ).fire( this.$element, $elements );
|
|
|
|
// Add the new items
|
|
this.getListWidget().addItems( itemWidgets );
|
|
};
|
|
|
|
/**
|
|
* Respond to model remove event. This may happen when an item
|
|
* is marked as read.
|
|
*
|
|
* @param {mw.echo.dm.NotificationItem[]} items Notification item models
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.onModelDiscardItems = function ( items ) {
|
|
const itemWidgets = [];
|
|
|
|
for ( let i = 0; i < items.length; i++ ) {
|
|
itemWidgets.push( this.listWidget.getItemFromId( items[ i ].getId() ) );
|
|
}
|
|
this.listWidget.removeItems( itemWidgets );
|
|
};
|
|
|
|
/**
|
|
* Get the associated list widget. This is useful to specifically
|
|
* add and/or remove items from the list.
|
|
*
|
|
* @return {mw.echo.ui.SortedListWidget} List widget
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.getListWidget = function () {
|
|
return this.listWidget;
|
|
};
|
|
|
|
/**
|
|
* Get the timestamp for the list
|
|
*
|
|
* @return {number} Timestamp
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.getTimestamp = function () {
|
|
return this.model.getTimestamp();
|
|
};
|
|
|
|
/**
|
|
* Toggle the visibility of the title
|
|
*
|
|
* @param {boolean} show Show the title
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.toggleTitle = function ( show ) {
|
|
show = show !== undefined ? show : !this.showTitle;
|
|
|
|
if ( this.showTitle !== show ) {
|
|
this.showTitle = show;
|
|
this.title.toggle( this.showTitle );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a the source of this list.
|
|
*
|
|
* @return {string} Group source
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.getSource = function () {
|
|
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 model symbolic name.
|
|
* This is meant for sorting callbacks that fallback on
|
|
* sorting by IDs.
|
|
*
|
|
* @return {string} Group source
|
|
*/
|
|
mw.echo.ui.SubGroupListWidget.prototype.getId = function () {
|
|
return this.model.getName();
|
|
};
|
|
}() );
|