mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-12-22 12:43:02 +00:00
2dd3780966
Change-Id: I01da3714b1f5d9b1926ee4394c15f594efb01245
245 lines
6.7 KiB
JavaScript
245 lines
6.7 KiB
JavaScript
( function () {
|
|
/**
|
|
* Notifications list widget.
|
|
* All of its items must be of the mw.echo.ui.NotificationItem type.
|
|
*
|
|
* @class
|
|
* @extends mw.echo.ui.SortedListWidget
|
|
*
|
|
* @constructor
|
|
* @param {mw.echo.Controller} controller Echo notifications controller
|
|
* @param {mw.echo.dm.ModelManager} manager Model manager
|
|
* @param {Object} [config] Configuration object
|
|
* marked as read when they are seen.
|
|
* @param {string} [config.timestamp=0] A timestamp representing the latest item in
|
|
* the list.
|
|
* @param {jQuery} [config.$overlay] A jQuery element functioning as an overlay
|
|
* for popups.
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget = function MwEchoUiNotificationsListWidget( controller, manager, config ) {
|
|
config = config || {};
|
|
// Parent constructor
|
|
mw.echo.ui.NotificationsListWidget.super.call(
|
|
this,
|
|
// Sorting callback
|
|
( a, b ) => {
|
|
if ( !a.isRead() && b.isRead() ) {
|
|
return -1; // Unread items are always above read items
|
|
} else if ( a.isRead() && !b.isRead() ) {
|
|
return 1;
|
|
} else if ( !a.isForeign() && b.isForeign() ) {
|
|
return -1;
|
|
} else if ( a.isForeign() && !b.isForeign() ) {
|
|
return 1;
|
|
}
|
|
|
|
// 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
|
|
);
|
|
|
|
// Initialize models
|
|
this.controller = controller;
|
|
this.manager = manager;
|
|
this.models = {};
|
|
|
|
// Properties
|
|
this.$overlay = config.$overlay || this.$element;
|
|
this.timestamp = config.timestamp || 0;
|
|
|
|
// Dummy 'loading' option widget
|
|
this.loadingOptionWidget = new mw.echo.ui.PlaceholderItemWidget();
|
|
|
|
this.resetLoadingOption();
|
|
|
|
this.manager.connect( this, {
|
|
update: 'resetDataFromModel',
|
|
discard: 'onModelManagerDiscard'
|
|
} );
|
|
|
|
this.$element
|
|
.addClass( 'mw-echo-ui-notificationsListWidget' );
|
|
};
|
|
|
|
/* Initialization */
|
|
|
|
OO.inheritClass( mw.echo.ui.NotificationsListWidget, mw.echo.ui.SortedListWidget );
|
|
|
|
/* Events */
|
|
|
|
/**
|
|
* The content of this list has changed.
|
|
* This event is to state that not only has the content changed
|
|
* but the actual DOM has been manipulated.
|
|
*
|
|
* @event mw.echo.ui.NotificationsListWidget#modified
|
|
*/
|
|
|
|
/* Methods */
|
|
|
|
mw.echo.ui.NotificationsListWidget.prototype.onModelManagerDiscard = function ( modelName ) {
|
|
const items = this.getItems();
|
|
|
|
// For the moment, this is only relevant for xwiki bundles.
|
|
// Local single items will not get their entire model removed, but
|
|
// local bundles may - when that happens, the condition below should
|
|
// also deal with local bundles and removing them specifically
|
|
if ( modelName === 'xwiki' ) {
|
|
for ( let i = 0; i < items.length; i++ ) {
|
|
if ( items[ i ] instanceof mw.echo.ui.CrossWikiNotificationItemWidget ) {
|
|
this.removeItems( [ items[ i ] ] );
|
|
this.checkForEmptyNotificationsList();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.emit( 'modified' );
|
|
};
|
|
|
|
/**
|
|
* Respond to model manager update event.
|
|
* This event means we are repopulating the entire list and the
|
|
* associated models within it.
|
|
*
|
|
* @param {Object} models Object of new models to populate the
|
|
* list.
|
|
* @fires mw.echo.ui.NotificationsListWidget#modified
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget.prototype.resetDataFromModel = function ( models ) {
|
|
const itemWidgets = [];
|
|
let $elements = $();
|
|
|
|
// Detach all attached models
|
|
for ( const modelId in this.models ) {
|
|
this.detachModel( modelId );
|
|
}
|
|
|
|
// Attach and process new models
|
|
for ( const modelId in models ) {
|
|
const model = models[ modelId ];
|
|
this.attachModel( modelId, model );
|
|
|
|
// Build widgets based on the data in the model
|
|
if ( model.isGroup() ) {
|
|
let widget;
|
|
if ( model.isForeign() ) {
|
|
// One Widget to Rule Them All
|
|
widget = new mw.echo.ui.CrossWikiNotificationItemWidget(
|
|
this.controller,
|
|
model,
|
|
{
|
|
$overlay: this.$overlay,
|
|
animateSorting: this.animated
|
|
}
|
|
);
|
|
} else {
|
|
// local bundle
|
|
widget = new mw.echo.ui.BundleNotificationItemWidget(
|
|
this.controller,
|
|
model,
|
|
{
|
|
$overlay: this.$overlay,
|
|
bundle: false,
|
|
animateSorting: this.animated
|
|
}
|
|
);
|
|
}
|
|
itemWidgets.push( widget );
|
|
$elements = $elements.add( widget.$element );
|
|
} else {
|
|
const subItems = model.getItems();
|
|
// Separate widgets per item
|
|
for ( let i = 0; i < subItems.length; i++ ) {
|
|
const subItem = subItems[ i ];
|
|
const widget = new mw.echo.ui.SingleNotificationItemWidget(
|
|
this.controller,
|
|
subItem,
|
|
{
|
|
$overlay: this.$overlay,
|
|
bundle: false
|
|
}
|
|
);
|
|
itemWidgets.push( widget );
|
|
$elements = $elements.add( widget.$element );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset the current items and re-add the new item widgets
|
|
this.clearItems();
|
|
|
|
// fire render hook
|
|
mw.hook( 'ext.echo.notifications.beforeRender' ).fire( this.$element, $elements );
|
|
|
|
this.addItems( itemWidgets );
|
|
|
|
this.checkForEmptyNotificationsList();
|
|
|
|
this.emit( 'modified' );
|
|
};
|
|
|
|
/**
|
|
* Attach a model to the widget
|
|
*
|
|
* @param {string} modelId Symbolic name for the model
|
|
* @param {mw.echo.dm.SortedList} model Notifications list model
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget.prototype.attachModel = function ( modelId, model ) {
|
|
this.models[ modelId ] = model;
|
|
};
|
|
|
|
/**
|
|
* Detach a model from the widget
|
|
*
|
|
* @param {string} modelId Notifications list model
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget.prototype.detachModel = function ( modelId ) {
|
|
this.models[ modelId ].disconnect( this );
|
|
delete this.models[ modelId ];
|
|
};
|
|
|
|
/**
|
|
* Reset the loading 'dummy' option widget
|
|
*
|
|
* @param {string} [label] Label for the option widget
|
|
* @param {string} [link] Link for the option widget
|
|
* @fires mw.echo.ui.NotificationsListWidget#modified
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget.prototype.resetLoadingOption = function ( label, link ) {
|
|
this.loadingOptionWidget.setLabel( label || '' );
|
|
this.loadingOptionWidget.setLink( link || '' );
|
|
if ( this.isEmpty() ) {
|
|
this.addItems( [ this.loadingOptionWidget ] );
|
|
}
|
|
this.emit( 'modified' );
|
|
};
|
|
|
|
/**
|
|
* Check if the list of notifications is empty and udpate the placeholder
|
|
* widget as needed.
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget.prototype.checkForEmptyNotificationsList = function () {
|
|
this.resetLoadingOption( this.isEmpty() ? mw.msg( 'echo-notification-placeholder' ) : '' );
|
|
};
|
|
|
|
/**
|
|
* Reset the 'initiallyUnseen' state of all items
|
|
*/
|
|
mw.echo.ui.NotificationsListWidget.prototype.resetInitiallyUnseenItems = function () {
|
|
const itemWidgets = this.getItems();
|
|
|
|
for ( let i = 0; i < itemWidgets.length; i++ ) {
|
|
itemWidgets[ i ].resetInitiallyUnseen();
|
|
}
|
|
};
|
|
}() );
|