mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-25 00:05:29 +00:00
2583f803c3
Note: This change would potentially affect all skins including modern and legacy Vector and others. The status quo when a talk notification is present is to transform the talk link into an orange notification message. One option is to leave the talk link and **add** a notification instead of transforming it. This will result in two elements #pt-mytalk and #pt-talk-alert This will interfere with styles targeting #pt-mytalk .mw-echo-alert This impacts user styles but not anything in production. Bug: T283811 Change-Id: I3e4be1381f9a2e9986b94b3b13df5ed64d09a59d
315 lines
11 KiB
JavaScript
315 lines
11 KiB
JavaScript
/* eslint-disable no-jquery/no-global-selector */
|
|
mw.echo = mw.echo || {};
|
|
mw.echo.config = mw.echo.config || {};
|
|
// Set default max prioritized action links per item
|
|
mw.echo.config.maxPrioritizedActions = 2;
|
|
|
|
/**
|
|
* Initialise desktop Echo experience
|
|
*/
|
|
function initDesktop() {
|
|
'use strict';
|
|
var uri;
|
|
|
|
// Remove ?markasread=XYZ from the URL
|
|
try {
|
|
uri = new mw.Uri();
|
|
if ( uri.query.markasread !== undefined ) {
|
|
delete uri.query.markasread;
|
|
delete uri.query.markasreadwiki;
|
|
window.history.replaceState( null, document.title, uri );
|
|
}
|
|
} catch ( e ) {
|
|
// Catch problems when the URI is malformed (T261799)
|
|
// e.g. #/media/Fitxer:Campbells_Soup_Cans_MOMA_reduced_80%.jpg
|
|
}
|
|
|
|
// Activate ooui
|
|
$( function () {
|
|
var selectedWidget,
|
|
echoApi,
|
|
messageController,
|
|
alertController,
|
|
messageModelManager,
|
|
alertModelManager,
|
|
unreadMessageCounter,
|
|
unreadAlertCounter,
|
|
maxNotificationCount = require( './config.json' ).EchoMaxNotificationCount,
|
|
pollingRate = require( './config.json' ).EchoPollForUpdates,
|
|
documentTitle = document.title,
|
|
$existingAlertLink = $( '#pt-notifications-alert a' ),
|
|
$existingMessageLink = $( '#pt-notifications-notice a' ),
|
|
numAlerts = $existingAlertLink.attr( 'data-counter-num' ),
|
|
numMessages = $existingMessageLink.attr( 'data-counter-num' ),
|
|
badgeLabelAlerts = $existingAlertLink.attr( 'data-counter-text' ),
|
|
badgeLabelMessages = $existingMessageLink.attr( 'data-counter-text' ),
|
|
// eslint-disable-next-line no-jquery/no-class-state
|
|
hasUnseenAlerts = $existingAlertLink.hasClass( 'mw-echo-unseen-notifications' ),
|
|
// eslint-disable-next-line no-jquery/no-class-state
|
|
hasUnseenMessages = $existingMessageLink.hasClass( 'mw-echo-unseen-notifications' ),
|
|
// latestMessageNotifTime is the time of most recent notification that came when we called showNotificationSnippet last
|
|
// the function showNotificationSnippet returns the time of the latest notification and latestMessageNotifTime is updated
|
|
latestMessageNotifTime = new Date(),
|
|
latestAlertNotifTime = new Date(),
|
|
alertCount = parseInt( numAlerts ),
|
|
messageCount = parseInt( numMessages ),
|
|
loadingPromise = null,
|
|
// Store links
|
|
links = {
|
|
notifications: $existingAlertLink.attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
|
|
preferences: ( $( '#pt-preferences a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Preferences' ) ) +
|
|
'#mw-prefsection-echo'
|
|
};
|
|
|
|
function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
|
|
var totalCount = totalAlertCount + totalMessageCount,
|
|
convertedTotalCount,
|
|
newTitle = documentTitle;
|
|
|
|
if ( totalCount > 0 ) {
|
|
convertedTotalCount = totalCount <= maxNotificationCount ? totalCount : maxNotificationCount + 1;
|
|
convertedTotalCount = mw.msg( 'echo-badge-count', mw.language.convertNumber( convertedTotalCount ) );
|
|
newTitle = mw.msg( 'parentheses', convertedTotalCount ) + ' ' + documentTitle;
|
|
}
|
|
document.title = newTitle;
|
|
}
|
|
|
|
/**
|
|
* Show notification snippet via mw.notify of notifications which came after highestNotifTime.
|
|
*
|
|
* @param {mw.echo.dm.ModelManager} modelManager
|
|
* @param {Date} highestNotifTime Timestamp of latest notification the last time function was called
|
|
* @return {Date} Timestamp of latest notification
|
|
*/
|
|
function showNotificationSnippet( modelManager, highestNotifTime ) {
|
|
var timestampAsDate,
|
|
highestTime = new Date();
|
|
highestTime = highestNotifTime;
|
|
modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
|
|
timestampAsDate = new Date( notificationItem.timestamp );
|
|
if ( timestampAsDate > highestNotifTime ) {
|
|
if ( timestampAsDate > highestTime ) {
|
|
highestTime = timestampAsDate;
|
|
}
|
|
if ( !notificationItem.seen ) {
|
|
mw.notify( $.parseHTML( notificationItem.content.header ), { title: mw.msg( 'echo-displaysnippet-title' ) } );
|
|
}
|
|
}
|
|
}
|
|
);
|
|
return highestTime;
|
|
}
|
|
|
|
/**
|
|
* Change the seen state of badges if there are any unseen notifications.
|
|
*
|
|
* @param {mw.echo.dm.ModelManager} modelManager
|
|
* @param {mw.echo.ui.NotificationBadgeWidget} badgeWidget
|
|
*/
|
|
function updateBadgeState( modelManager, badgeWidget ) {
|
|
modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
|
|
if ( !notificationItem.isSeen() ) {
|
|
badgeWidget.updateBadgeSeenState( true );
|
|
}
|
|
} );
|
|
}
|
|
|
|
function isLivePollingFeatureEnabledOnWiki() {
|
|
return pollingRate !== 0;
|
|
}
|
|
|
|
/**
|
|
* User has opted in to preference to show notification snippets and update document title with unread count.
|
|
*
|
|
* Only useful when isLivePollingFeatureEnabledOnWiki() returns true.
|
|
*
|
|
* @return {boolean} User preference
|
|
*/
|
|
function userHasOptedInToLiveNotifications() {
|
|
return mw.user.options.get( 'echo-show-poll-updates' ) === '1';
|
|
}
|
|
|
|
// Change document title on initialization only when polling rate feature flag is non-zero.
|
|
if ( isLivePollingFeatureEnabledOnWiki() && userHasOptedInToLiveNotifications() ) {
|
|
updateDocumentTitleWithNotificationCount( alertCount, messageCount );
|
|
}
|
|
|
|
function loadEcho() {
|
|
if ( loadingPromise !== null ) {
|
|
return loadingPromise;
|
|
}
|
|
// This part executes only once, either when header icons are clicked or after completion of 60secs whichever occur first.
|
|
echoApi = new mw.echo.api.EchoApi();
|
|
|
|
loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( function () {
|
|
|
|
// Overlay
|
|
mw.echo.ui.$overlay.appendTo( document.body );
|
|
|
|
unreadAlertCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'alert', maxNotificationCount );
|
|
alertModelManager = new mw.echo.dm.ModelManager( unreadAlertCounter, { type: 'alert' } );
|
|
alertController = new mw.echo.Controller( echoApi, alertModelManager );
|
|
|
|
mw.echo.ui.alertWidget = new mw.echo.ui.NotificationBadgeWidget(
|
|
alertController,
|
|
alertModelManager,
|
|
links,
|
|
{
|
|
numItems: Number( numAlerts ),
|
|
convertedNumber: badgeLabelAlerts,
|
|
hasUnseen: hasUnseenAlerts,
|
|
badgeIcon: 'bell',
|
|
$overlay: mw.echo.ui.$overlay,
|
|
href: $existingAlertLink.attr( 'href' )
|
|
}
|
|
);
|
|
|
|
// Replace the link button with the ooui button
|
|
$existingAlertLink.parent().replaceWith( mw.echo.ui.alertWidget.$element );
|
|
|
|
alertModelManager.on( 'allTalkRead', function () {
|
|
// If there was a talk page notification, get rid of it
|
|
$( '#pt-talk-alert' ).remove();
|
|
} );
|
|
|
|
// listen to event countChange and change title only if polling rate is non-zero
|
|
if ( isLivePollingFeatureEnabledOnWiki() ) {
|
|
alertModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
|
|
alertController.fetchLocalNotifications().then( function () {
|
|
updateBadgeState( alertModelManager, mw.echo.ui.alertWidget );
|
|
if ( userHasOptedInToLiveNotifications() ) {
|
|
latestAlertNotifTime = showNotificationSnippet( alertModelManager, latestAlertNotifTime );
|
|
alertCount = count;
|
|
updateDocumentTitleWithNotificationCount( count, messageCount );
|
|
}
|
|
} );
|
|
} );
|
|
}
|
|
|
|
// Load message button and popup if messages exist
|
|
if ( $existingMessageLink.length ) {
|
|
unreadMessageCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'message', maxNotificationCount );
|
|
messageModelManager = new mw.echo.dm.ModelManager( unreadMessageCounter, { type: 'message' } );
|
|
messageController = new mw.echo.Controller( echoApi, messageModelManager );
|
|
|
|
mw.echo.ui.messageWidget = new mw.echo.ui.NotificationBadgeWidget(
|
|
messageController,
|
|
messageModelManager,
|
|
links,
|
|
{
|
|
$overlay: mw.echo.ui.$overlay,
|
|
numItems: Number( numMessages ),
|
|
hasUnseen: hasUnseenMessages,
|
|
badgeIcon: 'tray',
|
|
convertedNumber: badgeLabelMessages,
|
|
href: $existingMessageLink.attr( 'href' )
|
|
}
|
|
);
|
|
|
|
// Replace the link button with the ooui button
|
|
$existingMessageLink.parent().replaceWith( mw.echo.ui.messageWidget.$element );
|
|
|
|
// listen to event countChange and change title only if polling rate is non-zero
|
|
if ( isLivePollingFeatureEnabledOnWiki() ) {
|
|
messageModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
|
|
messageController.fetchLocalNotifications().then( function () {
|
|
updateBadgeState( messageModelManager, mw.echo.ui.messageWidget );
|
|
if ( userHasOptedInToLiveNotifications() ) {
|
|
latestMessageNotifTime = showNotificationSnippet( messageModelManager, latestMessageNotifTime );
|
|
messageCount = count;
|
|
updateDocumentTitleWithNotificationCount( alertCount, count );
|
|
}
|
|
} );
|
|
} );
|
|
}
|
|
}
|
|
} );
|
|
return loadingPromise;
|
|
|
|
}
|
|
|
|
// Respond to click on the notification button and load the UI on demand
|
|
$( '.mw-echo-notification-badge-nojs' ).on( 'click', function ( e ) {
|
|
var timeOfClick = mw.now(),
|
|
$badge = $( this ),
|
|
clickedSection = $badge.parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
|
|
if ( e.which !== 1 || $badge.data( 'clicked' ) ) {
|
|
return false;
|
|
}
|
|
|
|
$badge.data( 'clicked', true );
|
|
|
|
// Dim the badge while we load
|
|
$badge.addClass( 'mw-echo-notifications-badge-dimmed' );
|
|
|
|
// Fire the notification API requests
|
|
echoApi = new mw.echo.api.EchoApi();
|
|
echoApi.fetchNotifications( clickedSection )
|
|
.then( function ( data ) {
|
|
mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - timeOfClick );
|
|
return data;
|
|
} );
|
|
|
|
loadEcho().then( function () {
|
|
// Now that the module loaded, show the popup
|
|
selectedWidget = clickedSection === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
|
|
selectedWidget.once( 'finishLoading', function () {
|
|
// Log timing after notifications are shown
|
|
mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - timeOfClick );
|
|
} );
|
|
selectedWidget.popup.toggle( true );
|
|
mw.track( 'timing.MediaWiki.echo.overlay.ooui', mw.now() - timeOfClick );
|
|
|
|
if ( hasUnseenAlerts || hasUnseenMessages ) {
|
|
// Clicked on the flyout due to having unread notifications
|
|
// This is part of tracking how likely users are to click a badge with unseen notifications.
|
|
// The other part is the 'echo.unseen' counter, see EchoHooks::onSkinTemplateNavigationUniversal().
|
|
mw.track( 'counter.MediaWiki.echo.unseen.click' );
|
|
}
|
|
}, function () {
|
|
// Un-dim badge if loading failed
|
|
$badge.removeClass( 'mw-echo-notifications-badge-dimmed' );
|
|
} );
|
|
// Prevent default
|
|
return false;
|
|
} );
|
|
|
|
function pollForNotificationCountUpdates() {
|
|
alertController.refreshUnreadCount();
|
|
messageController.refreshUnreadCount();
|
|
// Make notification update after n*pollingRate(time in secs) where n depends on document.hidden
|
|
setTimeout( pollForNotificationCountUpdates, ( document.hidden ? 5 : 1 ) * pollingRate * 1000 );
|
|
}
|
|
|
|
function pollStart() {
|
|
if ( mw.config.get( 'skin' ) !== 'minerva' && isLivePollingFeatureEnabledOnWiki() ) {
|
|
// load widgets if not loaded already then start polling
|
|
loadEcho().then( pollForNotificationCountUpdates );
|
|
}
|
|
}
|
|
|
|
setTimeout( pollStart, 60 * 1000 );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
/**
|
|
* Initialise a mobile experience instead
|
|
*/
|
|
function initMobile() {
|
|
if ( !mw.user.isAnon() ) {
|
|
mw.loader.using( [ 'ext.echo.mobile', 'mobile.startup' ] ).then( function ( require ) {
|
|
require( 'ext.echo.mobile' )();
|
|
} );
|
|
}
|
|
}
|
|
|
|
$( function () {
|
|
if ( mw.config.get( 'wgMFMode' ) ) {
|
|
initMobile();
|
|
} else {
|
|
initDesktop();
|
|
}
|
|
} );
|