mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-30 18:45:07 +00:00
0ff7678377
There are two counters (ext.echo.unseen and ext.echo.unseen.click) that are incremented in two different places. It's hard to see how they're connected unless you know where to look. Add comments to each of these counters pointing to the other one and explaining what they're for. Change-Id: I0699cba85bf797e4f7ef42cc6a7a996aa35510f0
311 lines
11 KiB
JavaScript
311 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';
|
|
|
|
// Remove ?markasread=XYZ from the URL
|
|
var uri = new mw.Uri();
|
|
if ( uri.query.markasread !== undefined ) {
|
|
delete uri.query.markasread;
|
|
delete uri.query.markasreadwiki;
|
|
window.history.replaceState( null, document.title, uri );
|
|
}
|
|
|
|
// 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-mytalk a' )
|
|
.removeClass( 'mw-echo-alert' )
|
|
.text( mw.msg( 'mytalk' ) );
|
|
} );
|
|
|
|
// 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::onPersonalUrls().
|
|
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();
|
|
}
|
|
} );
|