Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1426612
ext.echo.init.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
ext.echo.init.js
View Options
/**
* @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
();
}
}
}
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, May 16, 13:28 (1 d, 21 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
00/70/074b236c4be58661b979d37bc547
Default Alt Text
ext.echo.init.js (11 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment