' )
// We are wrapping the actions in a 'row' div so that the
// internal pieces are also a table layout
.addClass( 'mw-echo-ui-notificationItemWidget-content-actions-row' )
.append(
this.actionsButtonSelectWidget.$element,
this.timestampWidget.$element,
this.menuPopupButtonWidget.$element
)
);
} else {
this.$actions.append(
this.actionsButtonSelectWidget.$element,
this.menuPopupButtonWidget.$element,
this.timestampWidget.$element
);
}
// Actions
let outsideMenuItemCounter = 0;
const secondaryUrls = this.model.getSecondaryUrls();
for ( let i = 0; i < secondaryUrls.length; i++ ) {
const urlObj = secondaryUrls[ i ];
// Items are placed outside the dotdotdot menu if they are
// prioritized explicitly, *except* for items inside a bundle
// (where all actions are inside the menu) or there are more than
// two prioritized actions (all others go into the menu)
const isOutsideMenu = !this.bundle &&
(
(
// Make sure we don't have too many prioritized items
urlObj.prioritized &&
outsideMenuItemCounter < mw.echo.config.maxPrioritizedActions
) ||
// If the number of total items are equal to or less than the
// maximum allowed, they all go outside the menu
// mw.echo.config.maxPrioritizedActions is 2 on desktop and 1 on mobile.
secondaryUrls.length <= mw.echo.config.maxPrioritizedActions
);
const linkButton = new mw.echo.ui.MenuItemWidget( {
type: urlObj.type,
actionData: urlObj.data,
icon: urlObj.icon || 'next',
label: urlObj.label,
tooltip: urlObj.tooltip,
description: urlObj.description,
url: urlObj.url,
prioritized: isOutsideMenu
} );
// Limit to 2 items outside the menu
if ( isOutsideMenu ) {
this.actionsButtonSelectWidget.addItems( [ linkButton ] );
this.actionsButtonSelectWidget.setTabIndex( 0 );
outsideMenuItemCounter++;
} else {
this.menuPopupButtonWidget.getMenu().addItems( [ linkButton ] );
}
}
if ( this.bundle ) {
// In a bundle, we have table layout, so the icon is
// inserted into the content, and the 'mark as read'
// button is not floating, and should be at the end
this.$content.append(
$icon,
$message,
this.$actions,
this.markAsReadButton.$element
);
this.$element.append( this.$content );
} else {
this.$content.append(
this.markAsReadButton.$element,
$message,
this.$actions
);
this.$element.append( $icon, this.$content );
}
// Events
this.actionsButtonSelectWidget.connect( this, { choose: 'onPopupButtonWidgetChoose' } );
this.menuPopupButtonWidget.getMenu().connect( this, { choose: 'onPopupButtonWidgetChoose' } );
this.markAsReadButton.connect( this, { click: 'onMarkAsReadButtonClick' } );
this.$element
.addClass( 'mw-echo-ui-notificationItemWidget' )
.toggleClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen', !this.model.isSeen() && !this.bundle )
.toggleClass( 'mw-echo-ui-notificationItemWidget-bundled', this.bundle );
if ( this.model.getPrimaryUrl() ) {
this.$element
.attr( 'href', this.model.getPrimaryUrl() )
.on( 'click', this.onPrimaryLinkClick.bind( this ) );
}
};
OO.inheritClass( mw.echo.ui.NotificationItemWidget, OO.ui.Widget );
// Make the whole item a link to get native link behaviour
mw.echo.ui.NotificationItemWidget.static.tagName = 'a';
/**
* Respond to primary link click.
* Override this in the descendents.
*
* @return {boolean} true
*/
mw.echo.ui.NotificationItemWidget.prototype.onPrimaryLinkClick = function () {
return true;
};
/**
* Manage a click on a dynamic secondary link.
* We can't know what the link intends us to do in the API, so we trust the 'apiParams'
* to tell the controller. When the link is clicked, we will pass the information on
* to the controller, which will manage whatever promise and action is needed.
*
* NOTE: The messages are parsed as HTML. If user-input is expected
* please make sure to properly escape it.
*
* @param {OO.ui.ButtonOptionWidget} item The selected item
*/
mw.echo.ui.NotificationItemWidget.prototype.onPopupButtonWidgetChoose = function ( item ) {
if ( !( item instanceof mw.echo.ui.MenuItemWidget ) ) {
// Other kinds of items may be added by subclasses
return;
}
const actionData = item && item.getActionData(),
messages = item && item.getConfirmationMessages(),
widget = this;
// Send to controller
item.pushPending();
this.controller.performDynamicAction( actionData, this.getModel().getSource() )
.then( function () {
let $title = $( '
' )
.addClass( 'mw-echo-ui-notificationItemWidget-notify-title' )
.append( $.parseHTML( messages.title ) ),
$description = $( '
' )
.addClass( 'mw-echo-ui-notificationItemWidget-notify-description' )
.append( $.parseHTML( messages.description ) ),
$confirmation;
// Get rid of the button
item.disconnect( this );
if ( item.isPrioritized() ) {
widget.actionsButtonSelectWidget.removeItems( [ item ] );
} else {
// It's inside the popup menu
widget.menuPopupButtonWidget.getMenu().removeItems( [ item ] );
}
// Make sure to hide either piece if it is empty
$title.toggle( !!$title.text() );
$description.toggle( !!$description.text() );
// Display confirmation
$confirmation = $( '
' )
.append( $title, $description );
// Send to mw.notify
mw.notify( $confirmation );
} );
};
/**
* Respond to mark as read button click
*/
mw.echo.ui.NotificationItemWidget.prototype.onMarkAsReadButtonClick = function () {
// If we're marking read or unread, the notification was already seen.
// Remove the animation class
this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
this.markRead( !this.model.isRead() );
};
/**
* Mark this notification as read
*
* @method
* @abstract
* @param {boolean} [isRead=true] Notification is marked as read
*/
mw.echo.ui.NotificationItemWidget.prototype.markRead = null;
/**
* Get the notification link
*
* @return {string} Notification link
*/
mw.echo.ui.NotificationItemWidget.prototype.getPrimaryUrl = function () {
return this.model.getPrimaryUrl();
};
/**
* Get the item id
*
* @return {number} Notification id
*/
mw.echo.ui.NotificationItemWidget.prototype.getTimestamp = function () {
return this.model.getTimestamp();
};
/**
* Get the notification Id
*
* @return {number} Notification id
*/
mw.echo.ui.NotificationItemWidget.prototype.getId = function () {
return this.model.getId();
};
/**
* Check whether this item is seen.
*
* @return {boolean} Item is seen
*/
mw.echo.ui.NotificationItemWidget.prototype.isSeen = function () {
return this.model.isSeen();
};
/**
* Check whether this item is read.
*
* @return {boolean} Item is read
*/
mw.echo.ui.NotificationItemWidget.prototype.isRead = function () {
return this.model.isRead();
};
/**
* Check whether this item is foreign.
*
* @return {boolean} Item is foreign
*/
mw.echo.ui.NotificationItemWidget.prototype.isForeign = function () {
return this.model.isForeign();
};
/**
* Toggle the function of the 'mark as read' buttons from 'mark as read' to 'mark as unread'
* and vice versa.
*
* @param {boolean} [showMarkAsRead] Show the 'mark as read' buttons
* - "false" means that the item is marked as read, whereby we show the user 'mark unread'
* buttons.
* - "true" means that the item is marked as unread and we show the user 'mark as read'
* buttons
*/
mw.echo.ui.NotificationItemWidget.prototype.toggleMarkAsReadButtons = function ( showMarkAsRead ) {
showMarkAsRead = showMarkAsRead !== undefined ? showMarkAsRead : !this.model.isRead();
this.markAsReadButton.toggleState( showMarkAsRead );
this.menuPopupButtonWidget.toggle( !this.menuPopupButtonWidget.getMenu().isEmpty() );
};
/**
* Toggle the read state of the widget
*
* @param {boolean} [read] The current read state. If not given, the state will
* become the opposite of its current state.
*/
mw.echo.ui.NotificationItemWidget.prototype.toggleRead = function ( read ) {
this.read = read !== undefined ? read : !this.read;
this.$element.toggleClass( 'mw-echo-ui-notificationItemWidget-unread', !this.read );
this.toggleMarkAsReadButtons( !this.read );
};
/**
* Toggle the seen state of the widget
*
* @param {boolean} [seen] The current seen state. If not given, the state will
* become the opposite of its current state.
*/
mw.echo.ui.NotificationItemWidget.prototype.toggleSeen = function ( seen ) {
this.seen = seen !== undefined ? seen : !this.seen;
this.$element
.toggleClass( 'mw-echo-ui-notificationItemWidget-unseen', !this.seen );
};
/**
* Get the model associated with this widget.
*
* @return {mw.echo.dm.NotificationItem} Item model
*/
mw.echo.ui.NotificationItemWidget.prototype.getModel = function () {
return this.model;
};
/**
* Disconnect events when widget is destroyed.
*/
mw.echo.ui.NotificationItemWidget.prototype.destroy = function () {
this.model.disconnect( this );
};
/**
* Remove the 'initiallyUnseen' class, which was only used for the
* unseen animation when the user has first seen it.
*/
mw.echo.ui.NotificationItemWidget.prototype.resetInitiallyUnseen = function () {
this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
};
/**
* Declares whether this widget is a cloned fake.
*
* @return {boolean} false
*/
mw.echo.ui.NotificationItemWidget.prototype.isFake = function () {
return false;
};
}() );