mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-25 00:05:29 +00:00
80ddee3956
Change-Id: I713aafb891306fc21a776a7d4622c6a039798753
358 lines
11 KiB
JavaScript
358 lines
11 KiB
JavaScript
( function () {
|
|
/**
|
|
* Cross-wiki notification item widget.
|
|
* This widget is expandable and displays groups of
|
|
* notification item lists by their sources.
|
|
*
|
|
* TODO: When we have local bundles (without groups of lists)
|
|
* we can separate the "expand" functionality and UI to another mixin
|
|
* so we can use it with both widgets.
|
|
*
|
|
* @class
|
|
* @extends mw.echo.ui.NotificationItemWidget
|
|
* @mixins OO.ui.mixin.PendingElement
|
|
*
|
|
* @constructor
|
|
* @param {mw.echo.Controller} controller Echo notifications controller
|
|
* @param {mw.echo.dm.CrossWikiNotificationItem} model Notification group model
|
|
* @param {Object} [config] Configuration object
|
|
* @cfg {boolean} [animateSorting=false] Animate the sorting of items
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget = function MwEchoUiCrossWikiNotificationItemWidget( controller, model, config ) {
|
|
config = config || {};
|
|
|
|
// Parent constructor
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.super.call( this, controller, model, config );
|
|
// Mixin constructors
|
|
OO.ui.mixin.PendingElement.call( this, config );
|
|
|
|
// In cross-wiki groups we only have 'mark as read'
|
|
this.toggleMarkAsReadButtons( true );
|
|
|
|
this.listWidget = new mw.echo.ui.SortedListWidget(
|
|
// Sorting callback
|
|
function ( a, b ) {
|
|
// Define the sorting order.
|
|
// This will go by the lists' timestamp, which in turn
|
|
// take the latest timestamp in their items
|
|
if ( b.getTimestamp() < a.getTimestamp() ) {
|
|
return -1;
|
|
} else if ( b.getTimestamp() > a.getTimestamp() ) {
|
|
return 1;
|
|
}
|
|
|
|
// Fallback on IDs
|
|
return b.getSource() - a.getSource();
|
|
},
|
|
// Config
|
|
{
|
|
classes: [ 'mw-echo-ui-crossWikiNotificationItemWidget-group' ],
|
|
timestamp: this.getTimestamp(),
|
|
$overlay: this.$overlay,
|
|
animated: config.animateSorting
|
|
}
|
|
);
|
|
|
|
this.listWidget.$element
|
|
// We have to manually set the display to 'none' here because
|
|
// otherwise the 'slideUp' and 'slideDown' jQuery effects don't
|
|
// work
|
|
.css( 'display', 'none' );
|
|
this.setPendingElement( this.listWidget.$element );
|
|
|
|
this.errorWidget = new mw.echo.ui.PlaceholderItemWidget();
|
|
this.errorWidget.toggle( false );
|
|
|
|
// Initialize closed
|
|
this.showTitles = true;
|
|
this.expanded = false;
|
|
this.fetchedOnce = false;
|
|
|
|
// Add "expand" button
|
|
this.toggleExpandButton = new OO.ui.ButtonOptionWidget( {
|
|
icon: 'expand',
|
|
framed: false,
|
|
classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-button' ]
|
|
} );
|
|
this.updateExpandButton();
|
|
this.actionsButtonSelectWidget.addItems( [ this.toggleExpandButton ] );
|
|
|
|
// Events
|
|
this.model.connect( this, { discard: 'onModelDiscard' } );
|
|
this.toggleExpandButton.connect( this, { click: 'expand' } );
|
|
this.$content.on( 'click', this.expand.bind( this ) );
|
|
|
|
// TODO: Handle cases where the group became empty or a case where the group only has 1 item left.
|
|
// Note: Right now this code works primarily for cross-wiki notifications. These act differently
|
|
// than local bundles. Cross-wiki notifications, when they "lose" their items for being read, they
|
|
// vanish from the list. Unlike them, the plan for local bundles is that read sub-items go outside
|
|
// the bundle and become their own items in the general notificationsWidget, and when the local bundle
|
|
// has 1 notification left, the group will actually transform into that last notification item.
|
|
// We don't listen to the empty event right now, because the entire item is deleted in cross-wiki
|
|
// notifications. When we work on local bundles, we will have to add that event listener per item.
|
|
|
|
// Initialization
|
|
this.populateFromModel();
|
|
this.toggleExpanded( false );
|
|
this.toggleRead( false );
|
|
this.toggleTitles( true );
|
|
this.$element
|
|
.addClass( 'mw-echo-ui-crossWikiNotificationItemWidget' )
|
|
.append(
|
|
$( '<div>' )
|
|
.addClass( 'mw-echo-ui-crossWikiNotificationItemWidget-separator' ),
|
|
this.listWidget.$element,
|
|
this.errorWidget.$element
|
|
);
|
|
};
|
|
|
|
/* Initialization */
|
|
|
|
OO.inheritClass( mw.echo.ui.CrossWikiNotificationItemWidget, mw.echo.ui.NotificationItemWidget );
|
|
OO.mixinClass( mw.echo.ui.CrossWikiNotificationItemWidget, OO.ui.mixin.PendingElement );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Respond to model removing source group
|
|
*
|
|
* @param {string} groupName Symbolic name of the group
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.onModelDiscard = function ( groupName ) {
|
|
var list = this.getList(),
|
|
group = list.getItemFromId( groupName );
|
|
|
|
list.removeItems( [ group ] );
|
|
|
|
if ( list.isEmpty() ) {
|
|
this.controller.removeCrossWikiItem();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.onMarkAsReadButtonClick = function () {
|
|
// Log this action
|
|
mw.echo.logger.logInteraction(
|
|
mw.echo.Logger.static.actions.markXWikiReadClick,
|
|
mw.echo.Logger.static.context.popup,
|
|
null, // Event ID is omitted
|
|
this.controller.getTypeString() // The type of the list in general
|
|
);
|
|
|
|
// Parent method
|
|
return mw.echo.ui.CrossWikiNotificationItemWidget.super.prototype.onMarkAsReadButtonClick.call( this );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.markRead = function () {
|
|
// Cross wiki notification is always only marked as read, never as
|
|
// unread. The original parameter is unneeded
|
|
this.controller.markEntireCrossWikiItemAsRead();
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.onPrimaryLinkClick = function () {
|
|
// Log notification click
|
|
|
|
mw.echo.logger.logInteraction(
|
|
mw.echo.Logger.static.actions.notificationClick,
|
|
mw.echo.Logger.static.context.popup,
|
|
this.getModel().getId(),
|
|
this.getModel().getCategory(),
|
|
false,
|
|
// Source of this notification if it is cross-wiki
|
|
this.bundle ? this.getModel().getSource() : ''
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Populate the items in this widget according to the data
|
|
* in the model
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.populateFromModel = function () {
|
|
var i,
|
|
groupWidgets = [],
|
|
groups = this.model.getList().getItems();
|
|
|
|
for ( i = 0; i < groups.length; i++ ) {
|
|
// Create SubGroup widgets
|
|
groupWidgets.push(
|
|
new mw.echo.ui.SubGroupListWidget(
|
|
this.controller,
|
|
groups[ i ],
|
|
{
|
|
$overlay: this.$overlay,
|
|
showTitle: this.showTitles
|
|
}
|
|
)
|
|
);
|
|
}
|
|
this.getList().addItems( groupWidgets );
|
|
};
|
|
|
|
/**
|
|
* Toggle the visibility of the titles
|
|
*
|
|
* @param {boolean} [show] Show titles
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.toggleTitles = function ( show ) {
|
|
var i,
|
|
items = this.getList().getItems();
|
|
|
|
show = show === undefined ? !this.showTitles : show;
|
|
|
|
if ( this.showTitles !== show ) {
|
|
this.showTitles = show;
|
|
for ( i = 0; i < items.length; i++ ) {
|
|
items[ i ].toggleTitle( show );
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check whether the titles should be shown and toggle them
|
|
* in each of the group lists.
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.checkShowTitles = function () {
|
|
this.toggleTitles( this.getList().getItemCount() > 1 );
|
|
};
|
|
|
|
/**
|
|
* Toggle the visibility of the notification item list and the placeholder/error widget.
|
|
*
|
|
* @param {boolean} showList Show the list
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.toggleListDisplay = function ( showList ) {
|
|
this.errorWidget.toggle( !showList );
|
|
this.listWidget.toggle( showList );
|
|
};
|
|
|
|
/**
|
|
* Show an error message
|
|
*
|
|
* @param {string} label Error label
|
|
* @param {string} link Error link
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.showErrorMessage = function ( label, link ) {
|
|
this.errorWidget.setLabel( label || '' );
|
|
this.errorWidget.setLink( link || '' );
|
|
|
|
this.toggleListDisplay( false );
|
|
};
|
|
|
|
/**
|
|
* Expand the group and fetch the list of notifications.
|
|
* Only fetch the first time we expand.
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.expand = function () {
|
|
var widget = this;
|
|
|
|
this.toggleExpanded( !this.expanded );
|
|
this.updateExpandButton();
|
|
|
|
if ( !this.expanded ) {
|
|
return;
|
|
}
|
|
|
|
// Log the expand action
|
|
mw.echo.logger.logInteraction(
|
|
mw.echo.Logger.static.actions.notificationBundleExpand,
|
|
mw.echo.Logger.static.context.popup,
|
|
widget.getModel().getId(),
|
|
widget.getModel().getCategory()
|
|
);
|
|
|
|
if ( !this.fetchedOnce ) {
|
|
// Expand
|
|
this.pushPending();
|
|
this.toggleListDisplay( true );
|
|
// Query all sources
|
|
this.controller.fetchCrossWikiNotifications()
|
|
.catch(
|
|
function ( result ) {
|
|
var loginPageTitle = mw.Title.newFromText( 'Special:UserLogin' );
|
|
// If failure, check if the failure is due to login
|
|
// so we can display a more comprehensive error
|
|
// message in that case
|
|
if ( result.errCode === 'notlogin-required' ) {
|
|
// Login error
|
|
widget.showErrorMessage(
|
|
mw.message( 'echo-notification-popup-loginrequired' ),
|
|
// Set the option link to the login page
|
|
loginPageTitle.getUrl()
|
|
);
|
|
} else {
|
|
// General error
|
|
widget.showErrorMessage( mw.msg( 'echo-api-failure' ) );
|
|
}
|
|
}
|
|
)
|
|
.always( this.popPending.bind( this ) );
|
|
|
|
// Only run the fetch notifications action once
|
|
this.fetchedOnce = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Toggle the expand/collapsed state of this group widget
|
|
*
|
|
* @param {boolean} show Show the widget expanded
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.toggleExpanded = function ( show ) {
|
|
this.expanded = show !== undefined ? !!show : !this.expanded;
|
|
|
|
this.$element.toggleClass( 'mw-echo-ui-crossWikiNotificationItemWidget-expanded', this.expanded );
|
|
|
|
if ( this.expanded ) {
|
|
// FIXME: Use CSS transition
|
|
// eslint-disable-next-line no-jquery/no-slide
|
|
this.getList().$element.slideDown();
|
|
} else {
|
|
// eslint-disable-next-line no-jquery/no-slide
|
|
this.getList().$element.slideUp();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update the expand button label
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.updateExpandButton = function () {
|
|
var type = this.model.getType();
|
|
|
|
this.toggleExpandButton.setLabel(
|
|
this.expanded ?
|
|
mw.msg( 'notification-link-text-collapse-all' ) :
|
|
// Messages that appear here are:
|
|
// * notification-link-text-expand-alert-count
|
|
// * notification-link-text-expand-notice-count
|
|
mw.msg(
|
|
'notification-link-text-expand-' +
|
|
( type === 'message' ? 'notice' : type ) +
|
|
'-count',
|
|
mw.language.convertNumber( this.model.getCount() )
|
|
)
|
|
);
|
|
this.toggleExpandButton.setIcon(
|
|
this.expanded ?
|
|
'collapse' :
|
|
'expand'
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Get the list widget contained in this item
|
|
*
|
|
* @return {mw.echo.ui.SortedListWidget} List widget
|
|
*/
|
|
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.getList = function () {
|
|
return this.listWidget;
|
|
};
|
|
}() );
|