mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-12-27 23:23:03 +00:00
2dd3780966
Change-Id: I01da3714b1f5d9b1926ee4394c15f594efb01245
308 lines
11 KiB
JavaScript
308 lines
11 KiB
JavaScript
/**
|
|
* @module module:ext.echo.init
|
|
*/
|
|
|
|
/* 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
|
|
const url = new URL( location.href );
|
|
if ( url.searchParams.has( 'markasread' ) ) {
|
|
url.searchParams.delete( 'markasread' );
|
|
url.searchParams.delete( 'markasreadwiki' );
|
|
history.replaceState( null, '', url );
|
|
}
|
|
|
|
// Activate OOUI
|
|
$( () => {
|
|
const 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
|
|
// 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'
|
|
};
|
|
let alertController, messageController,
|
|
latestMessageNotifTime = new Date(),
|
|
latestAlertNotifTime = new Date(),
|
|
alertCount = parseInt( numAlerts ),
|
|
messageCount = parseInt( numMessages ),
|
|
loadingPromise = null;
|
|
|
|
function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
|
|
const totalCount = totalAlertCount + totalMessageCount;
|
|
|
|
let newTitle = documentTitle;
|
|
if ( totalCount > 0 ) {
|
|
let 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 ) {
|
|
let highestTime = highestNotifTime;
|
|
modelManager.getLocalNotifications().forEach( ( notificationItem ) => {
|
|
const 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( ( 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.
|
|
const echoApi = new mw.echo.api.EchoApi();
|
|
|
|
loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( () => {
|
|
|
|
// Overlay
|
|
mw.echo.ui.$overlay.appendTo( document.body );
|
|
|
|
const unreadAlertCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'alert', maxNotificationCount );
|
|
const 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', () => {
|
|
// 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', ( count ) => {
|
|
alertController.fetchLocalNotifications().then( () => {
|
|
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 ) {
|
|
const unreadMessageCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'message', maxNotificationCount );
|
|
const 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', ( count ) => {
|
|
messageController.fetchLocalNotifications().then( () => {
|
|
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 ) {
|
|
const timeOfClick = mw.now(),
|
|
$badge = $( this ),
|
|
clickedSection = $badge.parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
|
|
if ( e.which !== 1 || $badge.data( 'clicked' ) ) {
|
|
// Do not return false (as that calls stopPropagation)
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
|
|
$badge.data( 'clicked', true );
|
|
|
|
// Dim the badge while we load
|
|
$badge.addClass( 'mw-echo-notifications-badge-dimmed' );
|
|
|
|
// Fire the notification API requests
|
|
const echoApi = new mw.echo.api.EchoApi();
|
|
echoApi.fetchNotifications( clickedSection )
|
|
.then( ( data ) => {
|
|
mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - timeOfClick );
|
|
return data;
|
|
} );
|
|
|
|
loadEcho().then( () => {
|
|
// Now that the module loaded, show the popup
|
|
const selectedWidget = clickedSection === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
|
|
selectedWidget.once( 'finishLoading', () => {
|
|
// 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' );
|
|
}
|
|
}, () => {
|
|
// Un-dim badge if loading failed
|
|
$badge.removeClass( 'mw-echo-notifications-badge-dimmed' );
|
|
} );
|
|
// Prevent default. Do not return false (as that calls stopPropagation)
|
|
e.preventDefault();
|
|
} );
|
|
|
|
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( ( require ) => {
|
|
require( 'ext.echo.mobile' ).init();
|
|
} );
|
|
}
|
|
}
|
|
|
|
$( () => {
|
|
if ( mw.config.get( 'wgMFMode' ) ) {
|
|
initMobile();
|
|
} else {
|
|
// Echo is intentionally disable for the Minerva desktop skin (https://phabricator.wikimedia.org/T343839)
|
|
// The suggested fix is documented in https://phabricator.wikimedia.org/T343838,
|
|
if ( mw.config.get( 'skin' ) !== 'minerva' ) {
|
|
initDesktop();
|
|
}
|
|
}
|
|
} );
|