summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Echo/modules')
-rw-r--r--Echo/modules/api/mw.echo.api.APIHandler.js10
-rw-r--r--Echo/modules/api/mw.echo.api.EchoApi.js6
-rw-r--r--Echo/modules/api/mw.echo.api.ForeignAPIHandler.js4
-rw-r--r--Echo/modules/api/mw.echo.api.LocalAPIHandler.js7
-rw-r--r--Echo/modules/api/mw.echo.api.NetworkHandler.js4
-rw-r--r--Echo/modules/api/mw.echo.api.PromisePrioritizer.js5
-rw-r--r--Echo/modules/api/mw.echo.api.js4
-rw-r--r--Echo/modules/controller/mw.echo.Controller.js19
-rw-r--r--Echo/modules/echo.mixins.less7
-rw-r--r--Echo/modules/echo.variables.less19
-rw-r--r--Echo/modules/ext.echo.init.js307
-rw-r--r--Echo/modules/ext.echo.moment-hack.js11
-rw-r--r--Echo/modules/icons/badgeicons.json15
-rw-r--r--Echo/modules/icons/bell.svg7
-rw-r--r--Echo/modules/icons/help.svg7
-rw-r--r--Echo/modules/icons/helpNotice-ltr.svg7
-rw-r--r--Echo/modules/icons/helpNotice-rtl.svg7
-rw-r--r--Echo/modules/icons/tray.svg7
-rw-r--r--Echo/modules/icons/unbell.svg7
-rw-r--r--Echo/modules/logger/mw.echo.Logger.js13
-rw-r--r--Echo/modules/mobile/NotificationBadge.js92
-rw-r--r--Echo/modules/mobile/NotificationBadge.mustache19
-rw-r--r--Echo/modules/mobile/list.js85
-rw-r--r--Echo/modules/mobile/notifications.js117
-rw-r--r--Echo/modules/mobile/notificationsFilterOverlay.js39
-rw-r--r--Echo/modules/mobile/notificationsFilterOverlay.less52
-rw-r--r--Echo/modules/mobile/overlay.js85
-rw-r--r--Echo/modules/mobile/overlay.less61
-rw-r--r--Echo/modules/model/mw.echo.dm.BundleNotificationItem.js9
-rw-r--r--Echo/modules/model/mw.echo.dm.CrossWikiNotificationItem.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.FiltersModel.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.ModelManager.js8
-rw-r--r--Echo/modules/model/mw.echo.dm.NotificationGroupsList.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.NotificationItem.js28
-rw-r--r--Echo/modules/model/mw.echo.dm.NotificationsList.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.PaginationModel.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.SeenTimeModel.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.SortedList.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.SourcePagesModel.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.UnreadNotificationCounter.js4
-rw-r--r--Echo/modules/model/mw.echo.dm.js4
-rw-r--r--Echo/modules/mw.echo.js4
-rw-r--r--Echo/modules/nojs/mw.echo.alert.less6
-rw-r--r--Echo/modules/nojs/mw.echo.alert.modern.less3
-rw-r--r--Echo/modules/nojs/mw.echo.badge.less62
-rw-r--r--Echo/modules/nojs/mw.echo.badge.minerva.less20
-rw-r--r--Echo/modules/nojs/mw.echo.badge.modern.less8
-rw-r--r--Echo/modules/nojs/mw.echo.badge.monobook.less46
-rw-r--r--Echo/modules/nojs/mw.echo.badge.vector.less16
-rw-r--r--Echo/modules/nojs/mw.echo.notifications.less18
-rw-r--r--Echo/modules/nojs/mw.echo.special.less12
-rw-r--r--Echo/modules/special/ext.echo.special.js11
-rw-r--r--Echo/modules/styles/mw.echo.ui.ConfirmationPopupWidget.less8
-rw-r--r--Echo/modules/styles/mw.echo.ui.CrossWikiNotificationItemWidget.less5
-rw-r--r--Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less2
-rw-r--r--Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.monobook.less7
-rw-r--r--Echo/modules/styles/mw.echo.ui.DatedNotificationsWidget.less3
-rw-r--r--Echo/modules/styles/mw.echo.ui.DatedSubGroupListWidget.less3
-rw-r--r--Echo/modules/styles/mw.echo.ui.FooterNoticeWidget.less5
-rw-r--r--Echo/modules/styles/mw.echo.ui.MenuItemWidget.less6
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.less4
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.modern.less18
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.monobook.less36
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationItemWidget.less24
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationItemWidget.modern.less25
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationsInboxWidget.less17
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationsListWidget.less2
-rw-r--r--Echo/modules/styles/mw.echo.ui.NotificationsListWidget.monobook.less4
-rw-r--r--Echo/modules/styles/mw.echo.ui.PageFilterWidget.less3
-rw-r--r--Echo/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less13
-rw-r--r--Echo/modules/styles/mw.echo.ui.PaginationWidget.less4
-rw-r--r--Echo/modules/styles/mw.echo.ui.PlaceholderItemWidget.less3
-rw-r--r--Echo/modules/styles/mw.echo.ui.SpecialHelpMenuWidget.less2
-rw-r--r--Echo/modules/styles/mw.echo.ui.SubGroupListWidget.less3
-rw-r--r--Echo/modules/styles/mw.echo.ui.ToggleReadCircleButtonWidget.less4
-rw-r--r--Echo/modules/styles/mw.echo.ui.mobile.less19
-rw-r--r--Echo/modules/styles/mw.echo.ui.overlay.minerva.less14
-rw-r--r--Echo/modules/ui/mw.echo.ui.ActionMenuPopupWidget.js29
-rw-r--r--Echo/modules/ui/mw.echo.ui.BadgeLinkWidget.js11
-rw-r--r--Echo/modules/ui/mw.echo.ui.BundleNotificationItemWidget.js7
-rw-r--r--Echo/modules/ui/mw.echo.ui.ClonedNotificationItemWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.ConfirmationPopupWidget.js6
-rw-r--r--Echo/modules/ui/mw.echo.ui.CrossWikiNotificationItemWidget.js11
-rw-r--r--Echo/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.DatedNotificationsWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.DatedSubGroupListWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.FooterNoticeWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.MenuItemWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.NotificationBadgeWidget.js19
-rw-r--r--Echo/modules/ui/mw.echo.ui.NotificationItemWidget.js16
-rw-r--r--Echo/modules/ui/mw.echo.ui.NotificationsInboxWidget.js6
-rw-r--r--Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.NotificationsWrapper.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.PageFilterWidget.js7
-rw-r--r--Echo/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.PaginationWidget.js5
-rw-r--r--Echo/modules/ui/mw.echo.ui.PlaceholderItemWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.ReadStateButtonSelectWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.SingleNotificationItemWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.SortedListWidget.js9
-rw-r--r--Echo/modules/ui/mw.echo.ui.SpecialHelpMenuWidget.js42
-rw-r--r--Echo/modules/ui/mw.echo.ui.SubGroupListWidget.js7
-rw-r--r--Echo/modules/ui/mw.echo.ui.ToggleReadCircleButtonWidget.js4
-rw-r--r--Echo/modules/ui/mw.echo.ui.js4
104 files changed, 1208 insertions, 571 deletions
diff --git a/Echo/modules/api/mw.echo.api.APIHandler.js b/Echo/modules/api/mw.echo.api.APIHandler.js
index 06760ae2..b80066d3 100644
--- a/Echo/modules/api/mw.echo.api.APIHandler.js
+++ b/Echo/modules/api/mw.echo.api.APIHandler.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Abstract notification API handler
*
@@ -77,7 +77,7 @@
* @param {string|string[]} [sources=*] Requested sources. If not given
* or if a '*' is given, all available sources will be queried
* @return {jQuery.Promise} Promise that is resolved with an object
- * of pages with the number of unread notifications per wiki
+ * of pages with the number of unread notifications per wiki
*/
mw.echo.api.APIHandler.prototype.fetchUnreadNotificationPages = function ( sources ) {
var params = {
@@ -107,11 +107,11 @@
return Array.isArray( sources ) ?
(
sources.indexOf( 'local' ) !== -1 ||
- sources.indexOf( mw.config.get( 'wgDBname' ) ) !== -1
+ sources.indexOf( mw.config.get( 'wgWikiID' ) ) !== -1
) :
(
sources === 'local' ||
- sources === mw.config.get( 'wgDBname' )
+ sources === mw.config.get( 'wgWikiID' )
);
};
@@ -272,4 +272,4 @@
mw.echo.api.APIHandler.prototype.getTypeParams = function ( type ) {
return this.typeParams[ type ];
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/api/mw.echo.api.EchoApi.js b/Echo/modules/api/mw.echo.api.EchoApi.js
index 80163d22..f0ef5181 100644
--- a/Echo/modules/api/mw.echo.api.EchoApi.js
+++ b/Echo/modules/api/mw.echo.api.EchoApi.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* A class defining Echo API instructions and network operations
*
@@ -60,7 +60,7 @@
*
* @param {string[]} [sources=all] Requested sources
* @return {jQuery.Promise} Promise that is resolved with an object
- * of pages with the number of unread notifications per wiki
+ * of pages with the number of unread notifications per wiki
*/
mw.echo.api.EchoApi.prototype.fetchUnreadNotificationPages = function ( sources ) {
return this.network.getApiHandler( 'local' ).fetchUnreadNotificationPages( sources )
@@ -338,4 +338,4 @@
mw.echo.api.EchoApi.prototype.getLimit = function () {
return this.limit;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/api/mw.echo.api.ForeignAPIHandler.js b/Echo/modules/api/mw.echo.api.ForeignAPIHandler.js
index b1da5e82..16d19a8b 100644
--- a/Echo/modules/api/mw.echo.api.ForeignAPIHandler.js
+++ b/Echo/modules/api/mw.echo.api.ForeignAPIHandler.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Foreign notification API handler
*
@@ -41,4 +41,4 @@
return $.extend( {}, this.typeParams[ type ], params );
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/api/mw.echo.api.LocalAPIHandler.js b/Echo/modules/api/mw.echo.api.LocalAPIHandler.js
index da86b1fe..c00ce740 100644
--- a/Echo/modules/api/mw.echo.api.LocalAPIHandler.js
+++ b/Echo/modules/api/mw.echo.api.LocalAPIHandler.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Notification API handler
*
@@ -40,7 +40,8 @@
mw.echo.api.LocalAPIHandler.prototype.updateSeenTime = function ( type ) {
type = Array.isArray( type ) ? type : [ type ];
- return this.api.postWithToken( 'csrf', {
+ // This is a GET request, not a POST request, for multi-DC support (see T222851)
+ return this.api.get( {
action: 'echomarkseen',
type: type.length === 1 ? type[ 0 ] : 'all',
timestampFormat: 'ISO_8601'
@@ -134,4 +135,4 @@
notcrosswikisummary: 1
} );
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/api/mw.echo.api.NetworkHandler.js b/Echo/modules/api/mw.echo.api.NetworkHandler.js
index 4875a001..fd204cc7 100644
--- a/Echo/modules/api/mw.echo.api.NetworkHandler.js
+++ b/Echo/modules/api/mw.echo.api.NetworkHandler.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Network handler for echo notifications. Manages multiple APIHandlers
* according to their sources.
@@ -76,4 +76,4 @@
mw.echo.api.NetworkHandler.prototype.setApiHandler = function ( name, handler ) {
this.handlers[ name ] = handler;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/api/mw.echo.api.PromisePrioritizer.js b/Echo/modules/api/mw.echo.api.PromisePrioritizer.js
index 2c7cda7a..91e3c0b3 100644
--- a/Echo/modules/api/mw.echo.api.PromisePrioritizer.js
+++ b/Echo/modules/api/mw.echo.api.PromisePrioritizer.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Promise prioritizer for API actions. The prioritizer takes
* a promise at a time, always prioritizing the latest promise and
@@ -23,6 +23,7 @@
/**
* Prioritize a promise
*
+ * @external Promise
* @param {jQuery.Promise|Promise} promise Promise
* @return {jQuery.Promise} The main deferred object that resolves
* or rejects when the latest promise is resolved or rejected.
@@ -85,4 +86,4 @@
} );
}
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/api/mw.echo.api.js b/Echo/modules/api/mw.echo.api.js
index 25abf260..f5129bce 100644
--- a/Echo/modules/api/mw.echo.api.js
+++ b/Echo/modules/api/mw.echo.api.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
mw.echo = mw.echo || {};
mw.echo.api = mw.echo.api || {};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/controller/mw.echo.Controller.js b/Echo/modules/controller/mw.echo.Controller.js
index 5f857edb..9714f29c 100644
--- a/Echo/modules/controller/mw.echo.Controller.js
+++ b/Echo/modules/controller/mw.echo.Controller.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/* global moment:false */
/**
* Controller for Echo notifications
@@ -91,11 +91,11 @@
foreignSources = {};
for ( source in data ) {
- if ( source !== mw.config.get( 'wgDBname' ) ) {
+ if ( source !== mw.config.get( 'wgWikiID' ) ) {
// Collect sources for API
foreignSources[ source ] = data[ source ].source;
}
- result[ source === mw.config.get( 'wgDBname' ) ? 'local' : source ] = data[ source ];
+ result[ source === mw.config.get( 'wgWikiID' ) ? 'local' : source ] = data[ source ];
}
// Register the foreign sources in the API
@@ -128,7 +128,7 @@
this.manager.getTypeString(),
currentSource,
{
- 'continue': continueValue,
+ continue: continueValue,
readState: filters.getReadState(),
titles: filters.getSourcePagesModel().getGroupedPagesForCurrentTitle()
}
@@ -335,7 +335,7 @@
// but is not an array. We should log this in the console
mw.log.warn(
'newNotifData.bundledNotifications is expected to be an array,' +
- 'but instead received "' + $.type( newNotifData.bundledNotifications ) + '"'
+ 'but instead received "' + typeof newNotifData.bundledNotifications + '"'
);
}
}
@@ -499,13 +499,11 @@
*/
mw.echo.Controller.prototype.markLocalNotificationsRead = function () {
var modelName, model,
- itemIds = [],
readState = this.manager.getFiltersModel().getReadState(),
modelItems = {};
this.manager.getLocalNotifications().forEach( function ( notification ) {
if ( !notification.isRead() ) {
- itemIds = itemIds.concat( notification.getAllIds() );
notification.toggleRead( true );
modelName = notification.getModelName();
@@ -525,9 +523,8 @@
// Update pagination count
this.manager.updateCurrentPageItemCount();
- this.manager.getUnreadCounter().estimateChange( -itemIds.length );
- this.manager.getLocalCounter().estimateChange( -itemIds.length );
- return this.api.markItemsRead( itemIds, 'local', true ).then( this.refreshUnreadCount.bind( this ) );
+ this.manager.getLocalCounter().setCount( 0, false );
+ return this.api.markAllRead( 'local', this.getTypeString() ).then( this.refreshUnreadCount.bind( this ) );
};
/**
@@ -791,4 +788,4 @@
mw.echo.Controller.prototype.getTypeString = function () {
return this.manager.getTypeString();
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/echo.mixins.less b/Echo/modules/echo.mixins.less
index 11a7f26a..38d80ba3 100644
--- a/Echo/modules/echo.mixins.less
+++ b/Echo/modules/echo.mixins.less
@@ -1,12 +1,7 @@
.mw-echo-ui-mixin-hover-opacity() {
opacity: @opacity-low;
+
&:hover {
opacity: 1;
}
}
-
-.mw-echo-ui-mixin-one-line-truncated() {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
diff --git a/Echo/modules/echo.variables.less b/Echo/modules/echo.variables.less
index a13776a4..2934f4bf 100644
--- a/Echo/modules/echo.variables.less
+++ b/Echo/modules/echo.variables.less
@@ -1,28 +1,31 @@
-// Taken from WikimediaUI base v0.6.1
+// = WikimediaUI base v0.15.0 excerpt
// Background Colors
@background-color-base: #fff;
// Foreground Colors
-@color-base: #222;
-@color-base-active: #000;
-@color-base-emphasized: @color-base-active;
+@color-base: #202122;
+@color-base--active: #000;
+@color-base--emphasized: @color-base--active;
+@color-base--inverted: #fff;
// Primary 'Progressive' Color, Background Color
@background-color-primary: #eaf3ff;
@color-primary: #36c;
// 'Destructive' Color
@color-destructive: #d33;
+// Border
// Border Colors
@border-color-heading: #c8ccd1;
+// Border Radius
+@border-radius-base: 2px;
-// Echo's original variables
+// = Echo's original variables
@grey-light: #72777d;
@grey-medium: #54595d;
@badge-padding: 0.12em;
@badge-icon-size: 1.1em;
@badge-distance-adjustment: 1em;
-@badge-offscreen-offset: 1000px;
@notification-item-background-read: #eaecf0;
@@ -34,7 +37,7 @@
@badge-counter-background-unseen-alert: @color-destructive;
@badge-counter-background-unseen-message: @color-primary;
-@notification-text-color: @color-base-emphasized;
+@notification-text-color: @color-base--emphasized;
@notification-body-color: @grey-light;
@bundle-group-padding: 0.7em;
@@ -50,4 +53,6 @@
@specialpage-width: 1000px;
+@background-color-popup-confirmation: #202122;
+
@border-color: @border-color-heading;
diff --git a/Echo/modules/ext.echo.init.js b/Echo/modules/ext.echo.init.js
index 76eeda1c..407bfd41 100644
--- a/Echo/modules/ext.echo.init.js
+++ b/Echo/modules/ext.echo.init.js
@@ -1,4 +1,13 @@
-( function ( mw, $ ) {
+/* 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
@@ -9,85 +18,128 @@
window.history.replaceState( null, document.title, uri );
}
- mw.echo = mw.echo || {};
- mw.echo.config = mw.echo.config || {};
- // Set default max prioritized action links per item
- mw.echo.config.maxPrioritizedActions = 2;
-
// Activate ooui
$( function () {
- var myWidget, echoApi,
+ 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: $( '#pt-notifications-alert a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
+ notifications: $existingAlertLink.attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
preferences: ( $( '#pt-preferences a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Preferences' ) ) +
'#mw-prefsection-echo'
};
- // Respond to click on the notification button and load the UI on demand
- $( '.mw-echo-notification-badge-nojs' ).click( function ( e ) {
- var time = mw.now(),
- myType = $( this ).parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
+ function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
+ var totalCount = totalAlertCount + totalMessageCount,
+ convertedTotalCount,
+ newTitle = documentTitle;
- if ( e.which !== 1 || $( this ).data( 'clicked' ) ) {
- return false;
+ 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;
+ }
- $( this ).data( 'clicked', true );
+ /**
+ * 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 );
+ }
+ } );
+ }
- // Dim the button while we load
- $( this ).addClass( 'mw-echo-notifications-badge-dimmed' );
+ function isLivePollingFeatureEnabledOnWiki() {
+ return pollingRate !== 0;
+ }
- // Fire the notification API requests
+ /**
+ * 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();
- echoApi.fetchNotifications( myType )
- .then( function ( data ) {
- mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - time );
- return data;
- } );
- // Load the ui
- mw.loader.using( 'ext.echo.ui.desktop', function () {
- var messageController,
- alertController,
- messageModelManager,
- alertModelManager,
- unreadMessageCounter,
- unreadAlertCounter,
- maxNotificationCount = mw.config.get( 'wgEchoMaxNotificationCount' );
+ loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( function () {
// Overlay
- $( 'body' ).append( mw.echo.ui.$overlay );
- // 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.$overlay.appendTo( document.body );
- 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 );
- }
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 );
@@ -106,6 +158,9 @@
}
);
+ // 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' )
@@ -113,27 +168,143 @@
.text( mw.msg( 'mytalk' ) );
} );
- // Replace the link button with the ooui button
- $existingAlertLink.parent().replaceWith( mw.echo.ui.alertWidget.$element );
+ // 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 );
+ }
+ } );
+ } );
+ }
- // HACK: Now that the module loaded, show the popup
- myWidget = myType === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
- myWidget.once( 'finishLoading', function () {
- // Log timing after notifications are shown
- mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - time );
- } );
- myWidget.popup.toggle( true );
- mw.track( 'timing.MediaWiki.echo.overlay.ooui', mw.now() - time );
+ // 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;
+
+ }
- if ( hasUnseenAlerts || hasUnseenMessages ) {
- // Clicked on the flyout due to having unread notifications
- mw.track( 'counter.MediaWiki.echo.unseen.click' );
+ // 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 );
+
} );
-}( mediaWiki, jQuery ) );
+}
+
+/**
+ * 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();
+ }
+} );
diff --git a/Echo/modules/ext.echo.moment-hack.js b/Echo/modules/ext.echo.moment-hack.js
index bd6baba9..8226193c 100644
--- a/Echo/modules/ext.echo.moment-hack.js
+++ b/Echo/modules/ext.echo.moment-hack.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/* global moment:false */
'use strict';
@@ -20,6 +20,13 @@
y: 'years',
yy: 'years'
};
+ // The following messages are used here:
+ // * notification-timestamp-ago-seconds
+ // * notification-timestamp-ago-minutes
+ // * notification-timestamp-ago-hours
+ // * notification-timestamp-ago-days
+ // * notification-timestamp-ago-months
+ // * notification-timestamp-ago-years
return mw.msg( 'notification-timestamp-ago-' + keymap[ key ], mw.language.convertNumber( number ) );
},
calendar: {
@@ -45,4 +52,4 @@
} );
// Reset back to original locale
moment.locale( momentOrigLocale );
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/icons/badgeicons.json b/Echo/modules/icons/badgeicons.json
deleted file mode 100644
index 135261be..00000000
--- a/Echo/modules/icons/badgeicons.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "variants": {
- "invert": {
- "color": "#FFFFFF"
- }
- },
- "images": {
- "bell": {
- "file": "icons/bell.svg"
- },
- "tray": {
- "file": "icons/tray.svg"
- }
- }
-}
diff --git a/Echo/modules/icons/bell.svg b/Echo/modules/icons/bell.svg
deleted file mode 100644
index bab71465..00000000
--- a/Echo/modules/icons/bell.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
- <title>
- bell
- </title>
- <path d="M16 7a5.38 5.38 0 0 0-4.46-4.85C11.6 1.46 11.53 0 10 0S8.4 1.46 8.46 2.15A5.38 5.38 0 0 0 4 7v6l-2 2v1h16v-1l-2-2zm-6 13a3 3 0 0 0 3-3H7a3 3 0 0 0 3 3z"/>
-</svg>
diff --git a/Echo/modules/icons/help.svg b/Echo/modules/icons/help.svg
deleted file mode 100644
index 94820366..00000000
--- a/Echo/modules/icons/help.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
- <title>
- help
- </title>
- <path d="M10 0a10 10 0 1 0 10 10A10 10 0 0 0 10 0zm1 16H9v-2h2zm2.71-7.6a2.63 2.63 0 0 1-.34.74 3.06 3.06 0 0 1-.48.55l-.54.48c-.21.18-.41.35-.59.52a3 3 0 0 0-.47.56A2.49 2.49 0 0 0 11 12a4.12 4.12 0 0 0-.11 1H9.08a8.68 8.68 0 0 1 .08-1.25 3.54 3.54 0 0 1 .24-.9 2.81 2.81 0 0 1 .41-.68 4.63 4.63 0 0 1 .58-.58l.51-.44a3 3 0 0 0 .44-.45 1.92 1.92 0 0 0 .3-.54 2.13 2.13 0 0 0 .11-.72 1.94 1.94 0 0 0-.18-.86 1.79 1.79 0 0 0-.43-.58 1.69 1.69 0 0 0-.54-.32 1.55 1.55 0 0 0-.5-.1 1.77 1.77 0 0 0-1.53.68 3 3 0 0 0-.49 1.82H6.16a4.84 4.84 0 0 1 .28-1.68 3.57 3.57 0 0 1 .8-1.29 3.62 3.62 0 0 1 1.27-.83A4.52 4.52 0 0 1 10.18 4a4.42 4.42 0 0 1 1.43.23 3.48 3.48 0 0 1 1.16.65 3 3 0 0 1 .78 1.06 3.49 3.49 0 0 1 .28 1.44 3.63 3.63 0 0 1-.12 1.02z"/>
-</svg>
diff --git a/Echo/modules/icons/helpNotice-ltr.svg b/Echo/modules/icons/helpNotice-ltr.svg
new file mode 100644
index 00000000..8f0c3dc8
--- /dev/null
+++ b/Echo/modules/icons/helpNotice-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
+ <title>
+ help
+ </title>
+ <path d="M10 0a10 10 0 1 0 10 10A10 10 0 0 0 10 0zm1 16H9v-2h2zm2.71-7.6a2.64 2.64 0 0 1-.33.74 3.16 3.16 0 0 1-.48.55l-.54.48c-.21.18-.41.35-.58.52a2.54 2.54 0 0 0-.47.56A2.3 2.3 0 0 0 11 12a3.79 3.79 0 0 0-.11 1H9.08a8.9 8.9 0 0 1 .07-1.25 3.28 3.28 0 0 1 .25-.9 2.79 2.79 0 0 1 .41-.67 4 4 0 0 1 .58-.58c.17-.16.34-.3.51-.44a3 3 0 0 0 .43-.44 1.83 1.83 0 0 0 .3-.55 2 2 0 0 0 .11-.72 2.06 2.06 0 0 0-.17-.86 1.71 1.71 0 0 0-1-.9 1.7 1.7 0 0 0-.5-.1 1.77 1.77 0 0 0-1.53.68 3 3 0 0 0-.5 1.82H6.16a4.74 4.74 0 0 1 .28-1.68 3.56 3.56 0 0 1 .8-1.29 3.88 3.88 0 0 1 1.28-.83A4.59 4.59 0 0 1 10.18 4a4.44 4.44 0 0 1 1.44.23 3.51 3.51 0 0 1 1.15.65 3.08 3.08 0 0 1 .78 1.06 3.54 3.54 0 0 1 .29 1.45 3.39 3.39 0 0 1-.13 1.01z"/>
+</svg>
diff --git a/Echo/modules/icons/helpNotice-rtl.svg b/Echo/modules/icons/helpNotice-rtl.svg
new file mode 100644
index 00000000..5a3122ab
--- /dev/null
+++ b/Echo/modules/icons/helpNotice-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
+ <title>
+ help
+ </title>
+ <path d="M0 10A10 10 0 1 0 10 0 10 10 0 0 0 0 10zm9 4h2v2H9zM6.16 7.39a3.54 3.54 0 0 1 .29-1.45 3.08 3.08 0 0 1 .78-1.06 3.51 3.51 0 0 1 1.15-.65A4.44 4.44 0 0 1 9.82 4a4.59 4.59 0 0 1 1.66.29 3.88 3.88 0 0 1 1.28.83 3.56 3.56 0 0 1 .8 1.29 4.74 4.74 0 0 1 .28 1.68h-1.91a3 3 0 0 0-.5-1.82 1.77 1.77 0 0 0-1.53-.68 1.7 1.7 0 0 0-.5.1 1.71 1.71 0 0 0-1 .9 2.06 2.06 0 0 0-.17.86 2 2 0 0 0 .11.72 1.83 1.83 0 0 0 .3.55 3 3 0 0 0 .43.44c.17.14.34.28.51.44a4 4 0 0 1 .58.58 2.79 2.79 0 0 1 .41.67 3.28 3.28 0 0 1 .25.9 8.9 8.9 0 0 1 .1 1.25H9.11A3.79 3.79 0 0 0 9 12a2.3 2.3 0 0 0-.31-.73 2.54 2.54 0 0 0-.47-.56c-.17-.17-.37-.34-.58-.52l-.54-.5a3.16 3.16 0 0 1-.48-.55 2.64 2.64 0 0 1-.33-.74 3.39 3.39 0 0 1-.13-1.01z"/>
+</svg>
diff --git a/Echo/modules/icons/tray.svg b/Echo/modules/icons/tray.svg
deleted file mode 100644
index b3209529..00000000
--- a/Echo/modules/icons/tray.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
- <title>
- tray
- </title>
- <path d="M17 1H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm0 12h-4l-1 2H8l-1-2H3V3h14z"/>
-</svg>
diff --git a/Echo/modules/icons/unbell.svg b/Echo/modules/icons/unbell.svg
new file mode 100644
index 00000000..8cb3e266
--- /dev/null
+++ b/Echo/modules/icons/unbell.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewbox="0 0 20 20">
+ <title>
+ unbell
+ </title>
+ <path d="M1.22 0L0 1.22l4.334 4.335A5.38 5.38 0 004 7v6l-2 2v1h12.78l4 4L20 18.78 17.22 16 5.23 4.01 1.22 0zM10 0C8.47 0 8.4 1.46 8.46 2.15a5.38 5.38 0 00-1.92.729l9.46 9.46V7a5.38 5.38 0 00-4.46-4.85C11.6 1.46 11.53 0 10 0zM7 17a3 3 0 003 3 3 3 0 003-3H7z"/>
+</svg>
diff --git a/Echo/modules/logger/mw.echo.Logger.js b/Echo/modules/logger/mw.echo.Logger.js
index cf380bbb..4c82926f 100644
--- a/Echo/modules/logger/mw.echo.Logger.js
+++ b/Echo/modules/logger/mw.echo.Logger.js
@@ -1,4 +1,5 @@
-( function ( $, mw ) {
+( function () {
+ var configVars = require( './config.json' );
mw.echo = mw.echo || {};
/**
@@ -28,7 +29,7 @@
* @static
* @property {boolean}
*/
- mw.echo.Logger.static.clickThroughEnabled = !!mw.config.get( 'wgEchoInteractionLogging' );
+ mw.echo.Logger.static.clickThroughEnabled = !!configVars.EchoInteractionLogging && !mw.user.isAnon();
/**
* Context definitions.
@@ -84,7 +85,7 @@
myEvt = {
action: action,
- version: mw.config.get( 'wgEchoEventLoggingVersion' ),
+ version: configVars.EchoEventLoggingVersion,
userId: +mw.config.get( 'wgUserId' ),
editCount: +mw.config.get( 'wgUserEditCount' )
};
@@ -103,7 +104,7 @@
myEvt.mobile = mobile || mw.config.get( 'skin' ) === 'minerva';
}
- if ( notifWiki && notifWiki !== mw.config.get( 'wgDBname' ) && notifWiki !== 'local' ) {
+ if ( notifWiki && notifWiki !== mw.config.get( 'wgWikiID' ) && notifWiki !== 'local' ) {
myEvt.notifWiki = notifWiki;
}
@@ -123,7 +124,7 @@
var i, len, key;
for ( i = 0, len = notificationIds.length; i < len; i++ ) {
- key = notifWiki && notifWiki !== mw.config.get( 'wgDBname' ) && notifWiki !== 'local' ?
+ key = notifWiki && notifWiki !== mw.config.get( 'wgWikiID' ) && notifWiki !== 'local' ?
notificationIds[ i ] + '-' + notifWiki :
notificationIds[ i ];
@@ -137,4 +138,4 @@
};
mw.echo.logger = new mw.echo.Logger();
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/mobile/NotificationBadge.js b/Echo/modules/mobile/NotificationBadge.js
new file mode 100644
index 00000000..fdffb010
--- /dev/null
+++ b/Echo/modules/mobile/NotificationBadge.js
@@ -0,0 +1,92 @@
+var
+ mobile = mw.mobileFrontend.require( 'mobile.startup' ),
+ View = mobile.View,
+ Icon = mobile.Icon,
+ notificationIcon = new Icon( {
+ name: 'bellOutline-base20',
+ glyphPrefix: 'wikimedia'
+ } );
+
+/**
+ * A notification button for displaying a notifications overlay
+ *
+ * @class NotificationButton
+ * @extends View
+ * @param {Object} options Configuration options
+ * @param {string} options.notificationIconClass e.g. mw-ui-icon for icon
+ * @param {boolean} options.hasUnseenNotifications whether the user has unseen notifications
+ * @param {number} options.notificationCountRaw number of unread notifications
+ * @param {string} options.title tooltip for badge
+ * @param {string} options.url to see all notifications
+ * @param {boolean} options.hasNotifications whether the user has unseen notifications
+ * @param {Function} options.onClick handler for when the badge is clicked
+ * @memberof NotificationBadge
+ * @instance
+ */
+function NotificationBadge( options ) {
+ var $el, $notificationAnchor,
+ count = options.notificationCountRaw || 0,
+ el = options.el;
+
+ if ( el ) {
+ // Learn properties based on current element
+ $el = $( el );
+ options.hasUnseenNotifications = $el.find( '.notification-unseen' ).length > 0;
+ options.hasNotifications = options.hasUnseenNotifications;
+ $notificationAnchor = $el.find( 'a' );
+ options.title = $notificationAnchor.attr( 'title' );
+ options.url = $notificationAnchor.attr( 'href' );
+ count = Number( $el.find( 'span' ).data( 'notification-count' ) );
+ }
+ View.call( this,
+ $.extend( {
+ notificationIconClass: notificationIcon.getClassName(),
+ hasNotifications: false,
+ hasUnseenNotifications: false,
+ notificationCountRaw: 0
+ }, options, {
+ isBorderBox: false
+ } )
+ );
+ this.url = options.url;
+ this.setCount( count );
+ if ( options.onClick ) {
+ this.$el.on( 'click', options.onClick );
+ }
+}
+
+OO.inheritClass( NotificationBadge, View );
+
+NotificationBadge.prototype.template = mw.template.get( 'ext.echo.mobile', 'NotificationBadge.mustache' );
+
+/**
+ * Update the notification count
+ *
+ * @memberof NotificationBadge
+ * @instance
+ * @param {number} count
+ */
+NotificationBadge.prototype.setCount = function ( count ) {
+ if ( count > 100 ) {
+ count = 100;
+ }
+ this.options.notificationCountRaw = count;
+ this.options.notificationCountString = mw.message( 'echo-badge-count',
+ mw.language.convertNumber( count )
+ ).text();
+ this.options.isNotificationCountZero = count === 0;
+ this.render();
+};
+
+/**
+ * Marks all notifications as seen
+ *
+ * @memberof NotificationBadge
+ * @instance
+ */
+NotificationBadge.prototype.markAsSeen = function () {
+ this.options.hasUnseenNotifications = false;
+ this.render();
+};
+
+module.exports = NotificationBadge;
diff --git a/Echo/modules/mobile/NotificationBadge.mustache b/Echo/modules/mobile/NotificationBadge.mustache
new file mode 100644
index 00000000..18045e2c
--- /dev/null
+++ b/Echo/modules/mobile/NotificationBadge.mustache
@@ -0,0 +1,19 @@
+{{^hasNotifications}}
+<a href="{{url}}" title="{{title}}"
+ class="{{notificationIconClass}} mw-echo-notification-badge-nojs user-button" id="user-notifications" data-event-name="ui.notifications">
+ {{title}}
+</a>
+{{/hasNotifications}}
+{{#hasNotifications}}
+<a href="{{url}}" title="{{title}}"
+ id="user-notifications"
+ data-event-name="ui.notifications"
+ class="mw-echo-notification-badge-nojs mw-echo-unseen-notifications notification-count user-button {{#hasUnseenNotifications}}notification-unseen{{/hasUnseenNotifications}}
+ {{#isNotificationCountZero}}zero{{/isNotificationCountZero}}">
+ <div class="circle">
+ <span data-notification-count="{{notificationCountRaw}}">
+ {{notificationCountString}}
+ </span>
+ </div>
+</a>
+{{/hasNotifications}}
diff --git a/Echo/modules/mobile/list.js b/Echo/modules/mobile/list.js
new file mode 100644
index 00000000..4660c0d8
--- /dev/null
+++ b/Echo/modules/mobile/list.js
@@ -0,0 +1,85 @@
+var mobile = mw.mobileFrontend.require( 'mobile.startup' ),
+ util = mobile.util,
+ View = mobile.View,
+ promisedView = mobile.promisedView;
+
+/**
+ * This callback is displayed as a global member.
+ *
+ * @callback FunctionCountChangeCallback
+ * @param {number} count a capped (0-99 or 99+) count
+ */
+
+/**
+ * List of notifications
+ *
+ * @param {mw.echo} echo class
+ * @param {OO.ui.ButtonWidget} markAllReadButton - a button that will be associated with the
+ * read status of the notifications list.
+ * @param {FunctionCountChangeCallback} onCountChange callback.
+ * @param {Function} onNotificationListRendered a function that is called when the
+ * notifications list has fully rendered (taking no arguments)
+ * @return {View}
+ */
+function notificationsList( echo, markAllReadButton, onCountChange, onNotificationListRendered ) {
+ var wrapperWidget,
+ maxNotificationCount = mw.config.get( 'wgEchoMaxNotificationCount' ),
+ echoApi = new echo.api.EchoApi(),
+ unreadCounter = new echo.dm.UnreadNotificationCounter( echoApi, 'all', maxNotificationCount ),
+ modelManager = new echo.dm.ModelManager( unreadCounter, { type: [ 'message', 'alert' ] } ),
+ controller = new echo.Controller(
+ echoApi,
+ modelManager,
+ {
+ type: [ 'message', 'alert' ]
+ }
+ ),
+ markAsReadHandler = function () {
+ markAllReadButton.toggle(
+ controller.manager.hasLocalUnread()
+ );
+ },
+ // Create a container which will be revealed when "more options" (...)
+ // is clicked on a notification. Hidden by default.
+ $moreOptions = util.parseHTML( '<div>' )
+ .addClass( 'notifications-overlay-overlay position-fixed' );
+
+ echo.config.maxPrioritizedActions = 1;
+
+ wrapperWidget = new echo.ui.NotificationsWrapper( controller, modelManager, {
+ $overlay: $moreOptions
+ } );
+
+ // Events
+ unreadCounter.on( 'countChange', function ( count ) {
+ onCountChange(
+ controller.manager.getUnreadCounter().getCappedNotificationCount( count )
+ );
+ markAsReadHandler();
+ } );
+ markAllReadButton.on( 'click', function () {
+ var numNotifications = controller.manager.getLocalUnread().length;
+
+ controller.markLocalNotificationsRead()
+ .then( function () {
+ mw.notify( mw.msg( 'echo-mark-all-as-read-confirmation', numNotifications ) );
+ markAllReadButton.toggle( false );
+ }, function () {
+ markAllReadButton.toggle( false );
+ } );
+ } );
+
+ return promisedView(
+ // Populate notifications
+ wrapperWidget.populate().then( function () {
+ controller.updateSeenTime();
+ onNotificationListRendered();
+ markAsReadHandler();
+ // Connect event here as we know that everything loaded correctly
+ modelManager.on( 'update', markAsReadHandler );
+ return View.make( {}, [ wrapperWidget.$element, $moreOptions ] );
+ } )
+ );
+}
+
+module.exports = notificationsList;
diff --git a/Echo/modules/mobile/notifications.js b/Echo/modules/mobile/notifications.js
new file mode 100644
index 00000000..9feaf9fb
--- /dev/null
+++ b/Echo/modules/mobile/notifications.js
@@ -0,0 +1,117 @@
+var NOTIFICATIONS_PATH = '/notifications';
+
+/**
+ * @fires echo.mobile every time the notifications overlay is opened
+ */
+function onOpenNotificationsOverlay() {
+ mw.hook( 'echo.mobile' ).fire( true );
+}
+
+/**
+ * @fires echo.mobile every time the notifications overlay is closed
+ */
+function onCloseNotificationsOverlay() {
+ mw.hook( 'echo.mobile' ).fire( false );
+}
+
+/*
+ * This code loads the necessary modules for the notifications overlay, not to be confused
+ * with the Toast notifications defined by common/toast.js.
+ */
+module.exports = function () {
+ var badge,
+ notificationsFilterOverlay = require( './notificationsFilterOverlay.js' ),
+ notificationsOverlay = require( './overlay.js' ),
+ router = require( 'mediawiki.router' ),
+ overlayManager = mw.mobileFrontend.require( 'mobile.startup' ).OverlayManager.getSingleton(),
+ NotificationBadge = require( './NotificationBadge.js' ),
+ initialized = false;
+
+ function showNotificationOverlay() {
+ var overlay = notificationsOverlay( badge.setCount.bind( badge ),
+ badge.markAsSeen.bind( badge ), function ( exit ) {
+ onCloseNotificationsOverlay();
+ exit();
+ } );
+ onOpenNotificationsOverlay();
+
+ return overlay;
+ }
+
+ // Once the DOM is loaded hijack the notifications button to display an overlay rather
+ // than linking to Special:Notifications.
+ $( function () {
+ badge = new NotificationBadge( {
+ onClick: function ( ev ) {
+ router.navigate( '#' + NOTIFICATIONS_PATH );
+ // prevent navigation to original Special:Notifications URL
+ // DO NOT USE stopPropagation or you'll break click tracking in WikimediaEvents
+ ev.preventDefault();
+ },
+ // eslint-disable-next-line no-jquery/no-global-selector
+ el: $( '#user-notifications.user-button' ).parent()
+ } );
+ overlayManager.add( /^\/notifications$/, showNotificationOverlay );
+
+ /**
+ * Adds a filter button to the UI inside notificationsInboxWidget
+ *
+ * @method
+ * @ignore
+ */
+ function addFilterButton() {
+ // Create filter button once the notifications overlay has been loaded
+ var filterStatusButton = new OO.ui.ButtonWidget(
+ {
+ href: '#/notifications-filter',
+ classes: [ 'mw-echo-ui-notificationsInboxWidget-main-toolbar-nav-filter-placeholder' ],
+ icon: 'funnel',
+ label: mw.msg( 'echo-mobile-notifications-filter-title' )
+ } );
+
+ // eslint-disable-next-line no-jquery/no-global-selector
+ $( '.mw-echo-ui-notificationsInboxWidget-cell-placeholder' ).append(
+ $( '<div>' )
+ .addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-nav-filter' )
+ .addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
+ .append( filterStatusButton.$element )
+ );
+ }
+
+ // This code will currently only be invoked on Special:Notifications
+ // The code is bundled here since it makes use of loadModuleScript. This also allows
+ // the possibility of invoking the filter from outside the Special page in future.
+ // Once the 'ext.echo.special.onInitialize' hook has fired, load notification filter.
+ mw.hook( 'ext.echo.special.onInitialize' ).add( function () {
+ // eslint-disable-next-line no-jquery/no-global-selector
+ var $crossWikiUnreadFilter = $( '.mw-echo-ui-crossWikiUnreadFilterWidget' ),
+ // eslint-disable-next-line no-jquery/no-global-selector
+ $notifReadState = $( '.mw-echo-ui-notificationsInboxWidget-main-toolbar-readState' );
+
+ // The 'ext.echo.special.onInitialize' hook is fired whenever special page notification
+ // changes display on click of a filter.
+ // Hence the hook is restricted from firing more than once.
+ if ( initialized ) {
+ return;
+ }
+
+ // setup the filter button (now we have OOjs UI)
+ addFilterButton();
+
+ // setup route
+ overlayManager.add( /^\/notifications-filter$/, function () {
+ onOpenNotificationsOverlay();
+ return notificationsFilterOverlay( {
+ onBeforeExit: function ( exit ) {
+ onCloseNotificationsOverlay();
+ exit();
+ },
+ $notifReadState: $notifReadState,
+ $crossWikiUnreadFilter: $crossWikiUnreadFilter
+ } );
+ } );
+ initialized = true;
+ } );
+ } );
+
+};
diff --git a/Echo/modules/mobile/notificationsFilterOverlay.js b/Echo/modules/mobile/notificationsFilterOverlay.js
new file mode 100644
index 00000000..20167bd9
--- /dev/null
+++ b/Echo/modules/mobile/notificationsFilterOverlay.js
@@ -0,0 +1,39 @@
+var Overlay = mw.mobileFrontend.require( 'mobile.startup' ).Overlay;
+
+/**
+ * Overlay for notifications filter
+ *
+ * @class NotificationsFilterOverlay
+ * @param {Object} options
+ * @param {Function} options.onBeforeExit executes before overlay closes
+ * @param {jQuery.Object} options.$notifReadState - notification read status widgets
+ * @param {jQuery.Object} options.$crossWikiUnreadFilter - notification unread filter
+ *
+ */
+function notificationsFilterOverlay( options ) {
+ var $content, overlay;
+ // Initialize
+ options.$crossWikiUnreadFilter.on( 'click', function () {
+ overlay.hide();
+ } );
+
+ options.$notifReadState.find( '.oo-ui-buttonElement' ).on( 'click', function () {
+ overlay.hide();
+ } );
+
+ $content = $( '<div>' ).append(
+ $( '<div>' )
+ .addClass( 'notifications-filter-overlay-read-state' )
+ .append( options.$notifReadState ),
+ options.$crossWikiUnreadFilter
+ );
+
+ overlay = Overlay.make( {
+ onBeforeExit: options.onBeforeExit,
+ heading: '<strong>' + mw.message( 'echo-mobile-notifications-filter-title' ).escaped() + '</strong>',
+ className: 'overlay notifications-filter-overlay notifications-overlay navigation-drawer'
+ }, { $el: $content } );
+ return overlay;
+}
+
+module.exports = notificationsFilterOverlay;
diff --git a/Echo/modules/mobile/notificationsFilterOverlay.less b/Echo/modules/mobile/notificationsFilterOverlay.less
new file mode 100644
index 00000000..ae06d254
--- /dev/null
+++ b/Echo/modules/mobile/notificationsFilterOverlay.less
@@ -0,0 +1,52 @@
+@import 'mediawiki.ui/variables.less';
+@import 'mediawiki.mixins.less';
+
+.notifications-filter-overlay {
+ padding-top: 0;
+
+ &-read-state {
+ padding-top: 5em;
+ padding-bottom: 1.5em;
+ text-align: center;
+ }
+
+ .overlay-content {
+ // this is needed not only on iOS, that's why we repeat it here even though
+ // it's in Overlay.less too
+ overflow-y: scroll; // has to be scroll, not auto
+ position: absolute;
+ width: 100%;
+ padding-bottom: 0;
+
+ .mw-echo-ui-pageNotificationsOptionWidget {
+ .box-sizing( border-box );
+ border: 1px solid @colorGray12;
+ padding: 0.7em 1.5em 1em 1em;
+ width: 100%;
+ }
+
+ .mw-echo-ui-notificationsInboxWidget-main-toolbar-readState {
+ display: block;
+ }
+
+ .mw-echo-ui-crossWikiUnreadFilterWidget {
+ border: 1px solid @colorGray12;
+ width: 100%;
+ margin-left: 0;
+ padding: 0;
+
+ &-title {
+ display: block;
+ font-size: 1.3em;
+ font-weight: bold;
+ margin-left: 0.7em;
+ }
+
+ &-subtitle {
+ display: block;
+ color: @colorGray1;
+ margin-left: 0.9em;
+ }
+ }
+ }
+}
diff --git a/Echo/modules/mobile/overlay.js b/Echo/modules/mobile/overlay.js
new file mode 100644
index 00000000..36080900
--- /dev/null
+++ b/Echo/modules/mobile/overlay.js
@@ -0,0 +1,85 @@
+var mobile = mw.mobileFrontend.require( 'mobile.startup' ),
+ Overlay = mobile.Overlay,
+ list = require( './list.js' ),
+ promisedView = mobile.promisedView,
+ View = mobile.View,
+ Anchor = mobile.Anchor;
+
+/**
+ * @param {Overlay} overlay
+ * @param {Function} exit
+ * @return {void}
+ */
+function onBeforeExitAnimation( overlay, exit ) {
+ if ( 'transition' in overlay.$el[ 0 ].style ) {
+ // Manually detach the overlay from DOM once hide animation completes.
+ overlay.$el[ 0 ].addEventListener( 'transitionend', exit, { once: true } );
+
+ // Kick off animation.
+ overlay.$el[ 0 ].classList.remove( 'visible' );
+ } else {
+ exit();
+ }
+}
+
+/**
+ * This callback is displayed as a global member.
+ *
+ * @callback FunctionCountChangeCallback
+ * @param {number} count a capped (0-99 or 99+) count
+ */
+
+/**
+ * Make a notification overlay
+ *
+ * @param {FunctionCountChangeCallback} onCountChange receives one parameter - a capped (0-99 or 99+) count.
+ * @param {Function} onNotificationListRendered a function that is called when the
+ * notifications list has fully rendered (taking no arguments)
+ * @param {Function} onBeforeExit
+ * @return {Overlay}
+ */
+function notificationsOverlay( onCountChange, onNotificationListRendered, onBeforeExit ) {
+ var markAllReadButton, overlay,
+ oouiPromise = mw.loader.using( 'oojs-ui' ).then( function () {
+ markAllReadButton = new OO.ui.ButtonWidget( {
+ icon: 'checkAll',
+ title: mw.msg( 'echo-mark-all-as-read' )
+ } );
+ return View.make(
+ { class: 'notifications-overlay-header-markAllRead' },
+ [ markAllReadButton.$element ]
+ );
+ } ),
+ markAllReadButtonView = promisedView( oouiPromise );
+ // hide the button spinner as it is confusing to see in the top right corner
+ markAllReadButtonView.$el.hide();
+
+ overlay = Overlay.make(
+ {
+ heading: '<strong>' + mw.message( 'notifications' ).escaped() + '</strong>',
+ footerAnchor: new Anchor( {
+ href: mw.util.getUrl( 'Special:Notifications' ),
+ progressive: true,
+ additionalClassNames: 'footer-link notifications-archive-link',
+ label: mw.msg( 'echo-overlay-link' )
+ } ).options,
+ headerActions: [ markAllReadButtonView ],
+ isBorderBox: false,
+ className: 'overlay notifications-overlay navigation-drawer',
+ onBeforeExit: function ( exit ) {
+ onBeforeExit( function () {
+ onBeforeExitAnimation( overlay, exit );
+ } );
+ }
+ },
+ promisedView(
+ oouiPromise.then( function () {
+ return list( mw.echo, markAllReadButton, onCountChange,
+ onNotificationListRendered );
+ } )
+ )
+ );
+ return overlay;
+}
+
+module.exports = notificationsOverlay;
diff --git a/Echo/modules/mobile/overlay.less b/Echo/modules/mobile/overlay.less
new file mode 100644
index 00000000..264583b9
--- /dev/null
+++ b/Echo/modules/mobile/overlay.less
@@ -0,0 +1,61 @@
+// FIXME: some of this could be removed if we reused .page-list styling
+@import 'mediawiki.ui/variables.less';
+@import 'mediawiki.mixins.less';
+
+.notifications-overlay {
+ // position: fixed + sliding drawer causes weird rendering bugs in Android
+ // Browser (tested on 4.0.4 and 4.2.1)
+ padding-top: 0;
+
+ &-header-markAllRead {
+ padding-right: 1em;
+ }
+
+ &-overlay {
+ position: absolute;
+ bottom: 3em;
+ right: 0;
+ left: 0;
+ z-index: 1;
+
+ .mw-echo-ui-actionMenuPopupWidget-menu {
+ padding: 0.5em;
+ font-size: 1.5em;
+ box-shadow: none;
+ border: 1px solid @colorGray10;
+ // Override the positioning of the menu
+ // so it is "stuck" on the bottom of the
+ // notification overlay panel
+ position: static !important;
+ bottom: 0 !important;
+ left: 0 !important;
+ width: 100% !important;
+ height: auto !important;
+ top: auto !important;
+ }
+ }
+
+ .overlay-content {
+ // this is needed not only on iOS, that's why we repeat it here even though
+ // it's in Overlay.less too
+ overflow-y: scroll; // has to be scroll, not auto
+ position: absolute;
+ width: 100%;
+ padding-top: 0;
+ padding-bottom: 0;
+ max-width: none; // T219320
+
+ .mw-echo-ui-notificationsWidget {
+ height: 100%;
+ }
+ }
+
+ .user-button {
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding-right: 0;
+ // Should correspond to @z-indexOverOverlay in MobileFrontend
+ z-index: 2;
+ }
+}
diff --git a/Echo/modules/model/mw.echo.dm.BundleNotificationItem.js b/Echo/modules/model/mw.echo.dm.BundleNotificationItem.js
index 60a42395..27a4a82d 100644
--- a/Echo/modules/model/mw.echo.dm.BundleNotificationItem.js
+++ b/Echo/modules/model/mw.echo.dm.BundleNotificationItem.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Bundle notification item model. Contains a list of bundled notifications.
* Is expandable.
@@ -59,8 +59,9 @@
* @return {boolean} Whether this bundle is completely read
*/
mw.echo.dm.BundleNotificationItem.prototype.isRead = function () {
- var itemIsRead = function ( item ) { return item.isRead(); };
- return this.list.getItems().every( itemIsRead );
+ return this.list.getItems().every( function ( item ) {
+ return item.isRead();
+ } );
};
/**
@@ -158,4 +159,4 @@
return this.getModelName();
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.CrossWikiNotificationItem.js b/Echo/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
index 2ee599a8..e56b7f18 100644
--- a/Echo/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
+++ b/Echo/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Cross-wiki notification item model. Contains a list of sources,
* that each contain a list of notification items from that source.
@@ -162,4 +162,4 @@
return this.getList().isEmpty();
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.FiltersModel.js b/Echo/modules/model/mw.echo.dm.FiltersModel.js
index 62c3e0e8..a58d4135 100644
--- a/Echo/modules/model/mw.echo.dm.FiltersModel.js
+++ b/Echo/modules/model/mw.echo.dm.FiltersModel.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Filters model for displaying filtered notification list.
*
@@ -96,4 +96,4 @@
return this.sourcePagesModel;
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.ModelManager.js b/Echo/modules/model/mw.echo.dm.ModelManager.js
index 367be286..32cccf65 100644
--- a/Echo/modules/model/mw.echo.dm.ModelManager.js
+++ b/Echo/modules/model/mw.echo.dm.ModelManager.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* A container that manages all models that are involved in creating
* the notification list. There are currently two types of models:
@@ -152,8 +152,8 @@
* @param {Object} modelDefinitions An object defining the models
* The format for the definition object is:
* {
- * 'modelId': {mw.echo.dm.SortedList},
- * ...
+ * 'modelId': {mw.echo.dm.SortedList},
+ * ...
* }
*/
mw.echo.dm.ModelManager.prototype.setNotificationModels = function ( modelDefinitions ) {
@@ -481,4 +481,4 @@
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.NotificationGroupsList.js b/Echo/modules/model/mw.echo.dm.NotificationGroupsList.js
index c4f99a44..26cde594 100644
--- a/Echo/modules/model/mw.echo.dm.NotificationGroupsList.js
+++ b/Echo/modules/model/mw.echo.dm.NotificationGroupsList.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Notification groups list data structure.
* It contains mw.echo.dm.NotificationsList items
@@ -161,4 +161,4 @@
}
return null;
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.NotificationItem.js b/Echo/modules/model/mw.echo.dm.NotificationItem.js
index 568166f9..790e43e5 100644
--- a/Echo/modules/model/mw.echo.dm.NotificationItem.js
+++ b/Echo/modules/model/mw.echo.dm.NotificationItem.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/* global moment:false */
/**
* Notification item data structure.
@@ -30,14 +30,14 @@
* @cfg {string} [source] The source this notification is coming from, if it is foreign
* @cfg {Object[]} [secondaryUrls] An array of objects defining the secondary URLs
* for this notification. The secondary URLs are expected to have this structure:
- * {
- * "iconType": "userAvatar", // A symbolic name for the icon.
- * // Will render as oo-ui-icon-* class.
- * "label": "", // The label for the link
- * "prioritized": true/false, // Prioritized links are outside of the popup
- * // menu, whenever possible.
- * "url": "..." // The url for the secondary link
- * }
+ * {
+ * "iconType": "userAvatar", // A symbolic name for the icon.
+ * // Will render as oo-ui-icon-* class.
+ * "label": "", // The label for the link
+ * "prioritized": true/false, // Prioritized links are outside of the popup
+ * // menu, whenever possible.
+ * "url": "..." // The url for the secondary link
+ * }
*/
mw.echo.dm.NotificationItem = function MwEchoDmNotificationItem( id, config ) {
var fallbackDate = moment.utc().format( 'YYYY-MM-DD[T]HH:mm:ss[Z]' );
@@ -292,12 +292,12 @@
};
/**
- * Get the all ids contained in this notification
- *
- * @return {number[]}
- */
+ * Get the all ids contained in this notification
+ *
+ * @return {number[]}
+ */
mw.echo.dm.NotificationItem.prototype.getAllIds = function () {
return [ this.getId() ].concat( this.bundledIds || [] );
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.NotificationsList.js b/Echo/modules/model/mw.echo.dm.NotificationsList.js
index 341965da..f3b04e26 100644
--- a/Echo/modules/model/mw.echo.dm.NotificationsList.js
+++ b/Echo/modules/model/mw.echo.dm.NotificationsList.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Notifications list data structure.
*
@@ -268,4 +268,4 @@
return this.getSource() !== 'local';
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.PaginationModel.js b/Echo/modules/model/mw.echo.dm.PaginationModel.js
index eae7c35d..99a4b514 100644
--- a/Echo/modules/model/mw.echo.dm.PaginationModel.js
+++ b/Echo/modules/model/mw.echo.dm.PaginationModel.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Pagination model for echo notifications pages.
*
@@ -219,4 +219,4 @@
mw.echo.dm.PaginationModel.prototype.getItemsPerPage = function () {
return this.itemsPerPage;
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.SeenTimeModel.js b/Echo/modules/model/mw.echo.dm.SeenTimeModel.js
index e00702e9..2892b5d9 100644
--- a/Echo/modules/model/mw.echo.dm.SeenTimeModel.js
+++ b/Echo/modules/model/mw.echo.dm.SeenTimeModel.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* SeenTime model for Echo notifications
*
@@ -78,4 +78,4 @@
return this.types;
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.SortedList.js b/Echo/modules/model/mw.echo.dm.SortedList.js
index b6b1b073..758deb11 100644
--- a/Echo/modules/model/mw.echo.dm.SortedList.js
+++ b/Echo/modules/model/mw.echo.dm.SortedList.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Sorted list abstract data structure.
*
@@ -27,4 +27,4 @@
* @return {boolean} This list is a group
*/
mw.echo.dm.SortedList.prototype.isGroup = null;
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.SourcePagesModel.js b/Echo/modules/model/mw.echo.dm.SourcePagesModel.js
index 4d0f56be..2d3b2b49 100644
--- a/Echo/modules/model/mw.echo.dm.SourcePagesModel.js
+++ b/Echo/modules/model/mw.echo.dm.SourcePagesModel.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Source pages model for notification filtering
*
@@ -177,4 +177,4 @@
this.sources[ source ].pages[ page.title ] = page;
}
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.UnreadNotificationCounter.js b/Echo/modules/model/mw.echo.dm.UnreadNotificationCounter.js
index 3257df51..d0ccd345 100644
--- a/Echo/modules/model/mw.echo.dm.UnreadNotificationCounter.js
+++ b/Echo/modules/model/mw.echo.dm.UnreadNotificationCounter.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Echo notification UnreadNotificationCounter model
*
@@ -144,4 +144,4 @@
this.source = source;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/model/mw.echo.dm.js b/Echo/modules/model/mw.echo.dm.js
index 4d9e3d3f..32a99a38 100644
--- a/Echo/modules/model/mw.echo.dm.js
+++ b/Echo/modules/model/mw.echo.dm.js
@@ -1,3 +1,3 @@
-( function ( mw ) {
+( function () {
mw.echo.dm = {};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/mw.echo.js b/Echo/modules/mw.echo.js
index cd4101bb..e69de29b 100644
--- a/Echo/modules/mw.echo.js
+++ b/Echo/modules/mw.echo.js
@@ -1,4 +0,0 @@
-( function ( mw ) {
- mw.echo = mw.echo || {};
- mw.echo.config = mw.echo.config || { maxPrioritizedActions: 2 };
-}( mediaWiki ) );
diff --git a/Echo/modules/nojs/mw.echo.alert.less b/Echo/modules/nojs/mw.echo.alert.less
index c26c47dd..e0ee3cb1 100644
--- a/Echo/modules/nojs/mw.echo.alert.less
+++ b/Echo/modules/nojs/mw.echo.alert.less
@@ -1,7 +1,9 @@
+@import '../echo.variables.less';
+
.mw-echo-alert {
- border-radius: 2px;
background-color: #fc3;
+ color: @color-base--emphasized;
+ border-radius: @border-radius-base;
padding: 0.25em 0.8em 0.2em 0.8em;
- color: #222;
font-weight: normal;
}
diff --git a/Echo/modules/nojs/mw.echo.alert.modern.less b/Echo/modules/nojs/mw.echo.alert.modern.less
deleted file mode 100644
index 716369d1..00000000
--- a/Echo/modules/nojs/mw.echo.alert.modern.less
+++ /dev/null
@@ -1,3 +0,0 @@
-#pt-mytalk a.mw-echo-alert {
- border-radius: 0;
-}
diff --git a/Echo/modules/nojs/mw.echo.badge.less b/Echo/modules/nojs/mw.echo.badge.less
index 130977d7..87a8b507 100644
--- a/Echo/modules/nojs/mw.echo.badge.less
+++ b/Echo/modules/nojs/mw.echo.badge.less
@@ -1,5 +1,5 @@
-@import '../echo.variables';
-@import 'mediawiki.mixins';
+@import '../echo.variables.less';
+@import 'mediawiki.mixins.less';
/* stylelint-disable no-descending-specificity */
/* We have to include the #pt-notifications selector due to monobook */
@@ -12,37 +12,30 @@
height: 20px;
margin: 0 2px;
// Hide the text, but keep accessible for screen-readers
- // Later we put the icons back onscreen with an opposite offset
- top: -@badge-offscreen-offset - 5px;
+ // Later we put the counter back onscreen with a zero text-indent
+ top: -5px;
+ text-indent: -9999px;
+ border-radius: @border-radius-base;
cursor: pointer;
text-decoration: none;
line-height: normal;
.box-sizing( border-box );
+ opacity: 0.87;
+ color: transparent;
&:hover,
&:active,
&:focus {
outline: 0;
- -moz-outline-style: 0;
}
- &-dimmed {
- opacity: 0.4;
- }
+ &:focus {
+ .box-shadow( ~'0 0 0 1px #fff, 0 0 0 3px #36c' );
+ opacity: 1;
- // Background icon
- &:before {
- position: absolute;
- display: inline-block;
- cursor: pointer;
- opacity: 0.8;
- content: '';
- background-repeat: no-repeat;
- // Bring it back onscreen
- top: @badge-offscreen-offset;
- left: 0;
- width: 100%;
- height: 100%;
+ &:after {
+ border-color: #36c;
+ }
}
// Counter
@@ -51,27 +44,33 @@
display: inline-block;
cursor: pointer;
// Bring it back onscreen
- top: @badge-offscreen-offset + 9px;
+ top: 9px;
+ text-indent: 0;
left: 55%;
font-size: 0.9em;
font-weight: bold;
padding: 0 0.3em;
border: 1px solid #fff;
- border-radius: 2px;
+ border-radius: @border-radius-base;
background-color: @badge-counter-background-seen;
content: attr( data-counter-text );
color: #fff;
}
+ &-dimmed {
+ opacity: 0.4;
+ }
+
&.mw-echo-notifications-badge-long-label {
margin-right: 0.5em;
+
&:after {
left: 35%;
}
}
&.mw-echo-notifications-badge-all-read {
- opacity: 0.625;
+ opacity: 0.51;
&:after {
visibility: hidden;
@@ -79,17 +78,14 @@
}
}
- #pt-notifications-alert & {
- &:before {
- /* @embed */
- background-image: url( ../icons/bell.svg );
+ &.oo-ui-flaggedElement-unseen,
+ &.mw-echo-unseen-notifications {
+ #pt-notifications-alert &:after {
+ background-color: @badge-counter-background-unseen-alert;
}
- }
- #pt-notifications-notice & {
- &:before {
- /* @embed */
- background-image: url( ../icons/tray.svg );
+ #pt-notifications-notice &:after {
+ background-color: @badge-counter-background-unseen-message;
}
}
}
diff --git a/Echo/modules/nojs/mw.echo.badge.minerva.less b/Echo/modules/nojs/mw.echo.badge.minerva.less
new file mode 100644
index 00000000..b95fd810
--- /dev/null
+++ b/Echo/modules/nojs/mw.echo.badge.minerva.less
@@ -0,0 +1,20 @@
+#pt-notifications-alert {
+ .mw-echo-notifications-badge-dimmed,
+ .mw-echo-notifications-badge {
+ top: 0;
+ margin: 0;
+ }
+}
+
+#pt-notifications-alert.oo-ui-widget {
+ list-style: none;
+ width: 1.25em;
+ height: 1.25em;
+ padding: 0.75em;
+ vertical-align: middle;
+
+ a {
+ height: auto;
+ padding: 0;
+ }
+}
diff --git a/Echo/modules/nojs/mw.echo.badge.modern.less b/Echo/modules/nojs/mw.echo.badge.modern.less
deleted file mode 100644
index 48484017..00000000
--- a/Echo/modules/nojs/mw.echo.badge.modern.less
+++ /dev/null
@@ -1,8 +0,0 @@
-.mw-echo-notifications-badge {
- #pt-notifications-alert &,
- #pt-notifications-notice & {
- &:before {
- z-index: 0;
- }
- }
-}
diff --git a/Echo/modules/nojs/mw.echo.badge.monobook.less b/Echo/modules/nojs/mw.echo.badge.monobook.less
index 576d4901..93eec98e 100644
--- a/Echo/modules/nojs/mw.echo.badge.monobook.less
+++ b/Echo/modules/nojs/mw.echo.badge.monobook.less
@@ -1,36 +1,14 @@
-@import '../echo.variables';
-
-/* We have to include the #pt-notifications selector due to monobook */
-.mw-echo-notifications-badge {
- #pt-notifications-notice &,
- #pt-notifications-alert & {
- display: inline-block;
- width: 16px;
- height: 16px;
- top: -@badge-offscreen-offset - 1px;
-
- &:before {
- background-size: 16px 16px;
- }
-
- &:after {
- top: @badge-offscreen-offset + 3px;
- }
- }
-
- &.oo-ui-flaggedElement-unseen,
- &.mw-echo-unseen-notifications {
- #pt-notifications-alert &:after {
- background-color: @badge-counter-background-unseen-alert;
- }
-
- #pt-notifications-notice &:after {
- background-color: @badge-counter-background-unseen-message;
- }
+@import '../echo.variables.less';
+
+#pt-notifications-notice .mw-echo-notifications-badge,
+#pt-notifications-alert .mw-echo-notifications-badge {
+ display: inline-block;
+ background-size: 16px 16px;
+ background-repeat: no-repeat;
+ top: -1px;
+
+ &:after {
+ top: 6px;
+ left: 45%;
}
}
-
-#p-personal #pt-notifications-alert,
-#p-personal #pt-notifications-notice {
- margin-right: 0.2em;
-}
diff --git a/Echo/modules/nojs/mw.echo.badge.vector.less b/Echo/modules/nojs/mw.echo.badge.vector.less
index 0256eb62..90c7d23c 100644
--- a/Echo/modules/nojs/mw.echo.badge.vector.less
+++ b/Echo/modules/nojs/mw.echo.badge.vector.less
@@ -1,18 +1,4 @@
-@import '../echo.variables';
-
-/* We have to include the #pt-notifications selector due to monobook */
-.mw-echo-notifications-badge {
- &.oo-ui-flaggedElement-unseen,
- &.mw-echo-unseen-notifications {
- #pt-notifications-alert &:after {
- background-color: @badge-counter-background-unseen-alert;
- }
-
- #pt-notifications-notice &:after {
- background-color: @badge-counter-background-unseen-message;
- }
- }
-}
+@import '../echo.variables.less';
#p-personal #pt-notifications-alert,
#p-personal #pt-notifications-notice {
diff --git a/Echo/modules/nojs/mw.echo.notifications.less b/Echo/modules/nojs/mw.echo.notifications.less
index 4627fdc6..8defd104 100644
--- a/Echo/modules/nojs/mw.echo.notifications.less
+++ b/Echo/modules/nojs/mw.echo.notifications.less
@@ -1,6 +1,6 @@
-@import '../echo.variables';
-@import '../echo.mixins';
-@import 'mediawiki.mixins';
+@import '../echo.variables.less';
+@import '../echo.mixins.less';
+@import 'mediawiki.mixins.less';
// This is only valid for the Special:Notifications page in no-JS mode,
// where the formatting of the notifications include the following structure
@@ -20,6 +20,7 @@
margin-right: 10px;
margin-left: 10px;
}
+
.mw-echo-notification {
background-color: @background-color-base;
color: @grey-light;
@@ -33,7 +34,6 @@
padding-bottom: 10px;
/* Force container to expand to height of floated contents */
overflow: hidden;
- zoom: 1;
span.autocomment {
color: inherit;
@@ -54,9 +54,9 @@
}
.mw-echo-payload {
- .mw-echo-ui-mixin-one-line-truncated;
color: @notification-body-color;
margin-top: 4px;
+ .text-overflow( @visible: false );
}
.mw-echo-title,
@@ -65,11 +65,9 @@
line-height: 1.4em;
.plainlinks {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
max-width: 100%;
display: inline-block;
+ .text-overflow( @visible: false );
vertical-align: top;
}
}
@@ -81,11 +79,9 @@
.mw-echo-notification-footer-element {
display: inline-block;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
max-width: 15em;
margin-right: 0.5em;
+ .text-overflow( @visible: false );
}
}
}
diff --git a/Echo/modules/nojs/mw.echo.special.less b/Echo/modules/nojs/mw.echo.special.less
index 336635c6..641060c4 100644
--- a/Echo/modules/nojs/mw.echo.special.less
+++ b/Echo/modules/nojs/mw.echo.special.less
@@ -1,5 +1,5 @@
/* Echo specific CSS */
-@import '../echo.variables';
+@import '../echo.variables.less';
.mw-echo-special-container {
max-width: 600px;
@@ -15,12 +15,9 @@
}
}
-// Hide the help link and the preferences link
-// in the JS version, since both appear inside
+// Hide the preferences link
+// in the JS version, since it appear inside
// the cog menu
-.client-js #mw-indicator-mw-helplink {
- display: none;
-}
.client-js #mw-echo-pref-link,
.client-js #contentSub {
@@ -135,8 +132,8 @@ ul.mw-echo-special-notifications {
float: right;
display: inline-block;
margin: 0;
-
opacity: 0.5;
+
&:hover {
opacity: 1;
}
@@ -154,6 +151,7 @@ div.mw-htmlform-ooui-wrapper {
// Center 'preferences' in mobile special page
text-align: center;
}
+
.mw-echo-special-header-link {
display: none;
}
diff --git a/Echo/modules/special/ext.echo.special.js b/Echo/modules/special/ext.echo.special.js
index a0c9ba24..d6963e8f 100644
--- a/Echo/modules/special/ext.echo.special.js
+++ b/Echo/modules/special/ext.echo.special.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
'use strict';
/*!
@@ -8,6 +8,8 @@
var specialPageContainer,
limitNotifications = 50,
links = mw.config.get( 'wgNotificationsSpecialPageLinks' ),
+ // FIXME: Use CSS transition
+ // eslint-disable-next-line no-jquery/no-global-selector
$content = $( '#mw-content-text' ),
echoApi = new mw.echo.api.EchoApi( { limit: limitNotifications } ),
unreadCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, [ 'message', 'alert' ], limitNotifications ),
@@ -37,15 +39,14 @@
{
limit: limitNotifications,
$overlay: mw.echo.ui.$overlay,
- prefLink: links.preferences,
- helpLink: links.help
+ prefLink: links.preferences
}
);
// Overlay
- $( 'body' ).append( mw.echo.ui.$overlay );
+ $( document.body ).append( mw.echo.ui.$overlay );
// Notifications
$content.empty().append( specialPageContainer.$element );
} );
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/styles/mw.echo.ui.ConfirmationPopupWidget.less b/Echo/modules/styles/mw.echo.ui.ConfirmationPopupWidget.less
index b0bd8398..905ad86a 100644
--- a/Echo/modules/styles/mw.echo.ui.ConfirmationPopupWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.ConfirmationPopupWidget.less
@@ -1,3 +1,5 @@
+@import '../echo.variables.less';
+
.mw-echo-ui-confirmationPopupWidget {
position: relative;
bottom: 1em;
@@ -5,10 +7,10 @@
text-align: center;
&-popup {
- background-color: #222;
- color: #fff;
+ background-color: @background-color-popup-confirmation;
+ color: @color-base--inverted;
display: inline-block;
- border-radius: 2px;
+ border-radius: @border-radius-base;
padding: 0.5em 1em;
text-align: left;
diff --git a/Echo/modules/styles/mw.echo.ui.CrossWikiNotificationItemWidget.less b/Echo/modules/styles/mw.echo.ui.CrossWikiNotificationItemWidget.less
index 569aa327..b0b04fc2 100644
--- a/Echo/modules/styles/mw.echo.ui.CrossWikiNotificationItemWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.CrossWikiNotificationItemWidget.less
@@ -1,5 +1,5 @@
-@import 'mediawiki.mixins';
-@import '../echo.variables';
+@import 'mediawiki.mixins.less';
+@import '../echo.variables.less';
.mw-echo-ui-crossWikiNotificationItemWidget,
.mw-echo-ui-bundleNotificationItemWidget {
@@ -20,7 +20,6 @@
&-content {
// The icon is 30px
margin-left: 30px;
-
padding-bottom: 1em;
// 0.8em from ItemWidget, plus 0.8em
padding-left: 1.6em;
diff --git a/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less b/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less
index 859e604a..4b131945 100644
--- a/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less
@@ -1,4 +1,4 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
.mw-echo-ui-crossWikiUnreadFilterWidget {
border: 1px solid @border-color;
diff --git a/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.monobook.less b/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.monobook.less
deleted file mode 100644
index d9454f9c..00000000
--- a/Echo/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.monobook.less
+++ /dev/null
@@ -1,7 +0,0 @@
-@import '../echo.variables';
-
-.mw-echo-ui-crossWikiUnreadFilterWidget {
- .mw-echo-ui-pageNotificationsOptionWidget-count .oo-ui-labelWidget {
- padding: 0;
- }
-}
diff --git a/Echo/modules/styles/mw.echo.ui.DatedNotificationsWidget.less b/Echo/modules/styles/mw.echo.ui.DatedNotificationsWidget.less
index b740f9ab..fc2c1c27 100644
--- a/Echo/modules/styles/mw.echo.ui.DatedNotificationsWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.DatedNotificationsWidget.less
@@ -1,4 +1,5 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
+
.mw-echo-ui-datedNotificationsWidget {
min-height: 5em;
diff --git a/Echo/modules/styles/mw.echo.ui.DatedSubGroupListWidget.less b/Echo/modules/styles/mw.echo.ui.DatedSubGroupListWidget.less
index ff40fb30..a403f36c 100644
--- a/Echo/modules/styles/mw.echo.ui.DatedSubGroupListWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.DatedSubGroupListWidget.less
@@ -1,4 +1,5 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
+
.mw-echo-ui-datedSubGroupListWidget {
.mw-body-content &-title {
// Since the title is <h2> we want to specifically
diff --git a/Echo/modules/styles/mw.echo.ui.FooterNoticeWidget.less b/Echo/modules/styles/mw.echo.ui.FooterNoticeWidget.less
index ff8cd7b5..589d55b6 100644
--- a/Echo/modules/styles/mw.echo.ui.FooterNoticeWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.FooterNoticeWidget.less
@@ -1,5 +1,5 @@
-@import '../echo.variables';
-@import '../echo.mixins';
+@import '../echo.variables.less';
+@import '../echo.mixins.less';
.mw-echo-ui-footerNoticeWidget {
padding: 0.5em;
@@ -28,7 +28,6 @@
&-dismiss,
&-info {
.mw-echo-ui-mixin-hover-opacity();
-
vertical-align: top;
.oo-ui-iconElement-icon {
diff --git a/Echo/modules/styles/mw.echo.ui.MenuItemWidget.less b/Echo/modules/styles/mw.echo.ui.MenuItemWidget.less
index 5735dfbd..60da86f9 100644
--- a/Echo/modules/styles/mw.echo.ui.MenuItemWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.MenuItemWidget.less
@@ -1,5 +1,5 @@
-@import '../echo.variables';
-@import '../echo.mixins';
+@import '../echo.variables.less';
+@import '../echo.mixins.less';
/* stylelint-disable no-descending-specificity */
.mw-echo-ui-menuItemWidget {
@@ -11,7 +11,7 @@
// Override link colour
color: @color-base;
// Set max-width so buttons are truncated
- max-width: 15em;
+ max-width: 25em;
}
&-prioritized {
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.less b/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.less
index 00b9a2e9..65320518 100644
--- a/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.less
@@ -1,5 +1,5 @@
-@import 'mediawiki.mixins';
-@import '../echo.variables';
+@import 'mediawiki.mixins.less';
+@import '../echo.variables.less';
.mw-echo-ui-notificationBadgeButtonPopupWidget {
position: relative;
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.modern.less b/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.modern.less
deleted file mode 100644
index 6dc59143..00000000
--- a/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.modern.less
+++ /dev/null
@@ -1,18 +0,0 @@
-body #p-personal {
- overflow: visible;
-}
-
-#p-personal li.mw-echo-ui-notificationBadgeButtonPopupWidget {
- font-variant: normal;
- text-transform: none;
- font-weight: normal;
- // Badge
- > .oo-ui-buttonElement-button {
- border-radius: 0;
- }
- // Popup buttons
- .oo-ui-buttonElement-button:hover {
- // In modern, the hover color is white, which is unhelpful.
- color: #666;
- }
-}
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.monobook.less b/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.monobook.less
deleted file mode 100644
index 3320c953..00000000
--- a/Echo/modules/styles/mw.echo.ui.NotificationBadgeWidget.monobook.less
+++ /dev/null
@@ -1,36 +0,0 @@
-@import '../echo.variables';
-
-#p-personal li.mw-echo-ui-notificationBadgeButtonPopupWidget {
- text-transform: none;
- font-weight: normal;
-
- &-popup {
- // #p-personal li has a font-size of 0.75em, but we want the
- // font size here to be exactly 0.875em, so we must override
- // the parent em sizing by dividing.
- font-size: 0.875em / 0.75em;
-
- a.oo-ui-buttonElement-button {
- color: #333;
- }
- }
-
- a {
- background-color: initial;
- }
-
- // Badge
- > .oo-ui-buttonElement-button {
- &:hover {
- .oo-ui-labelElement-label {
- color: #000 !important;
- }
- }
- }
-
- // Footer
- .oo-ui-popupWidget-footer {
- // Override text-align that monobook uses for #p-personal li
- text-align: left;
- }
-}
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.less b/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.less
index 23f8e5ad..b5f1838e 100644
--- a/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.less
@@ -1,6 +1,6 @@
-@import 'mediawiki.mixins';
-@import '../echo.variables';
-@import '../echo.mixins';
+@import 'mediawiki.mixins.less';
+@import '../echo.variables.less';
+@import '../echo.mixins.less';
.mw-echo-ui-notificationItemWidget {
background-color: @notification-item-background-read;
@@ -8,7 +8,6 @@
white-space: normal;
padding: 0.8em 1em 0.5em 1em;
.box-sizing( border-box );
-
border: 1px solid #c8ccd1;
border-bottom: 0;
@@ -79,14 +78,16 @@
&-header {
color: @notification-text-color;
}
+
&-body {
+ color: @notification-body-color;
+ margin-top: 4px;
+
// In the popup only, truncate the text to one-line
// with ellipses
.mw-echo-ui-notificationBadgeButtonPopupWidget-popup & {
- .mw-echo-ui-mixin-one-line-truncated;
+ .text-overflow( @visible: false );
}
- color: @notification-body-color;
- margin-top: 4px;
}
}
@@ -102,6 +103,7 @@
& > &-buttons.oo-ui-buttonSelectWidget {
display: table-cell;
+ white-space: nowrap;
vertical-align: bottom;
}
@@ -125,12 +127,11 @@
display: table-cell;
vertical-align: bottom;
text-align: right;
- color: @color-base-emphasized;
+ color: @color-base--emphasized;
opacity: @opacity-low;
white-space: nowrap;
width: 100%;
}
-
}
}
@@ -176,7 +177,6 @@
height: 1.5em;
width: 1.5em;
}
-
}
&-content-message {
@@ -261,6 +261,7 @@
from {
background-color: @notification-background-unseen;
}
+
to {
background-color: @notification-background-unread;
}
@@ -270,6 +271,7 @@
from {
background-color: @notification-background-unseen;
}
+
to {
background-color: @notification-background-unread;
}
@@ -279,6 +281,7 @@
from {
background-color: @notification-background-unseen;
}
+
to {
background-color: @notification-background-read;
}
@@ -288,6 +291,7 @@
from {
background-color: @notification-background-unseen;
}
+
to {
background-color: @notification-background-read;
}
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.modern.less b/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.modern.less
deleted file mode 100644
index b6edb0df..00000000
--- a/Echo/modules/styles/mw.echo.ui.NotificationItemWidget.modern.less
+++ /dev/null
@@ -1,25 +0,0 @@
-/* stylelint-disable no-descending-specificity */
-#p-personal {
- .mw-echo-ui-notificationItemWidget {
- & a,
- & a.new {
- // Oh and double everything for :hover since Modern does that too.
- &,
- &:hover {
- // In modern, the hover color is white, which is unhelpful.
- color: #666;
- text-decoration: none;
- }
- }
- }
-
- // Override personal tools padding for links
- li .mw-echo-state a {
- padding: 0;
- }
-
- .mw-echo-ui-notificationBadgeButtonPopupWidget-footer a {
- color: #666;
- }
-}
-/* stylelint-enable no-descending-specificity */
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationsInboxWidget.less b/Echo/modules/styles/mw.echo.ui.NotificationsInboxWidget.less
index 618804b6..19219710 100644
--- a/Echo/modules/styles/mw.echo.ui.NotificationsInboxWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.NotificationsInboxWidget.less
@@ -1,4 +1,5 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
+
.mw-echo-ui-notificationsInboxWidget {
display: table;
width: 100%;
@@ -25,6 +26,7 @@
&-cell {
display: table-cell;
vertical-align: middle;
+
&-placeholder {
display: table-cell;
width: 100%;
@@ -41,6 +43,7 @@
display: none;
}
}
+
&-main {
vertical-align: top;
@@ -50,6 +53,14 @@
width: 100%;
}
+ &-readState {
+ display: inline-block;
+
+ .mw-echo-ui-readStateButtonSelectWidget {
+ white-space: nowrap;
+ }
+ }
+
&-settings {
padding-left: 1em;
}
@@ -70,14 +81,17 @@
text-align: center;
}
}
+
&-readState {
display: inline-block;
}
+
&-settings,
&-pagination {
display: inline-block;
margin-top: 1em;
}
+
&-settings {
.oo-ui-popupWidget-popup {
text-align: left;
@@ -85,6 +99,7 @@
}
}
}
+
&-toolbarWrapper {
height: 7em;
}
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.less b/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.less
index ced6f2db..3a79558f 100644
--- a/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.less
@@ -1,4 +1,4 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
.mw-echo-ui-notificationsListWidget {
.mw-echo-ui-notificationsListWidget-bundle & {
diff --git a/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.monobook.less b/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.monobook.less
deleted file mode 100644
index df796f2b..00000000
--- a/Echo/modules/styles/mw.echo.ui.NotificationsListWidget.monobook.less
+++ /dev/null
@@ -1,4 +0,0 @@
-.mw-echo-ui-notificationsListWidget {
- text-align: left;
- text-transform: none;
-}
diff --git a/Echo/modules/styles/mw.echo.ui.PageFilterWidget.less b/Echo/modules/styles/mw.echo.ui.PageFilterWidget.less
index ec60bc63..47eb7ac4 100644
--- a/Echo/modules/styles/mw.echo.ui.PageFilterWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.PageFilterWidget.less
@@ -1,4 +1,5 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
+
.mw-echo-ui-pageFilterWidget {
margin-top: 2 * @specialpage-separation-unit;
diff --git a/Echo/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less b/Echo/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less
index 304c0feb..44d51b45 100644
--- a/Echo/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less
@@ -1,5 +1,5 @@
-@import 'mediawiki.mixins';
-@import '../echo.variables';
+@import 'mediawiki.mixins.less';
+@import '../echo.variables.less';
.mw-echo-ui-pageNotificationsOptionWidget {
clear: both;
@@ -11,7 +11,7 @@
&-highlighted {
background-color: #eaecf0;
- color: @color-base-active;
+ color: @color-base--active;
}
&-selected {
@@ -55,11 +55,10 @@
&-label {
display: inline-block;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
// Sidebar width - icon width - twice padding - counter average width
max-width: calc( @specialpage-sidebar-width - 1.865em - 2 * @specialpage-separation-unit - 2em );
+ // Truncate text with ellipsis.
+ .text-overflow( @visible: false );
}
}
@@ -72,7 +71,7 @@
color: @grey-medium;
padding: 0.2em 0.5em;
margin-left: 0.5em;
- border-radius: 2px;
+ border-radius: @border-radius-base;
white-space: nowrap;
.oo-ui-optionWidget-selected & {
diff --git a/Echo/modules/styles/mw.echo.ui.PaginationWidget.less b/Echo/modules/styles/mw.echo.ui.PaginationWidget.less
index a88f9bb4..930064f3 100644
--- a/Echo/modules/styles/mw.echo.ui.PaginationWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.PaginationWidget.less
@@ -12,6 +12,10 @@
vertical-align: middle;
}
+ &-direction {
+ white-space: nowrap;
+ }
+
&-label {
padding: 0 0.5em;
white-space: nowrap;
diff --git a/Echo/modules/styles/mw.echo.ui.PlaceholderItemWidget.less b/Echo/modules/styles/mw.echo.ui.PlaceholderItemWidget.less
index 7ea644e4..b33b459e 100644
--- a/Echo/modules/styles/mw.echo.ui.PlaceholderItemWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.PlaceholderItemWidget.less
@@ -1,4 +1,5 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
+
.mw-echo-ui-placeholderItemWidget {
padding: 2em;
background-color: @notification-background-read;
diff --git a/Echo/modules/styles/mw.echo.ui.SpecialHelpMenuWidget.less b/Echo/modules/styles/mw.echo.ui.SpecialHelpMenuWidget.less
index de1fcd1b..27820f40 100644
--- a/Echo/modules/styles/mw.echo.ui.SpecialHelpMenuWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.SpecialHelpMenuWidget.less
@@ -1,4 +1,4 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
.mw-echo-ui-specialHelpMenuWidget-menu {
a.oo-ui-menuOptionWidget {
diff --git a/Echo/modules/styles/mw.echo.ui.SubGroupListWidget.less b/Echo/modules/styles/mw.echo.ui.SubGroupListWidget.less
index 083efbfa..1779e2ec 100644
--- a/Echo/modules/styles/mw.echo.ui.SubGroupListWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.SubGroupListWidget.less
@@ -1,7 +1,6 @@
-@import '../echo.variables';
+@import '../echo.variables.less';
.mw-echo-ui-subGroupListWidget {
-
&:not( :first-child ) {
padding-top: @bundle-group-padding;
}
diff --git a/Echo/modules/styles/mw.echo.ui.ToggleReadCircleButtonWidget.less b/Echo/modules/styles/mw.echo.ui.ToggleReadCircleButtonWidget.less
index ecee6410..96582329 100644
--- a/Echo/modules/styles/mw.echo.ui.ToggleReadCircleButtonWidget.less
+++ b/Echo/modules/styles/mw.echo.ui.ToggleReadCircleButtonWidget.less
@@ -1,5 +1,5 @@
-@import 'mediawiki.mixins';
-@import '../echo.variables';
+@import 'mediawiki.mixins.less';
+@import '../echo.variables.less';
.mw-echo-ui-toggleReadCircleButtonWidget {
&-circle {
diff --git a/Echo/modules/styles/mw.echo.ui.mobile.less b/Echo/modules/styles/mw.echo.ui.mobile.less
new file mode 100644
index 00000000..54b9562f
--- /dev/null
+++ b/Echo/modules/styles/mw.echo.ui.mobile.less
@@ -0,0 +1,19 @@
+@import 'mediawiki.ui/variables';
+
+// `!important` rules override the inline styles provides by clippable.
+@media all and ( max-width: @width-breakpoint-tablet ) {
+ .mw-echo-ui-overlay {
+ .oo-ui-clippableElement-clippable {
+ width: 100% !important;
+ }
+
+ .oo-ui-popupWidget-popup {
+ width: 100% !important;
+ }
+
+ .mw-echo-ui-notificationBadgeButtonPopupWidget-popup {
+ left: 1px !important;
+ right: 2px;
+ }
+ }
+}
diff --git a/Echo/modules/styles/mw.echo.ui.overlay.minerva.less b/Echo/modules/styles/mw.echo.ui.overlay.minerva.less
index 27177f7c..95d1817a 100644
--- a/Echo/modules/styles/mw.echo.ui.overlay.minerva.less
+++ b/Echo/modules/styles/mw.echo.ui.overlay.minerva.less
@@ -1,4 +1,15 @@
-/* In mobile, overlay is positioned at the bottom */
+@import 'mediawiki.ui/variables';
+
+@media all and ( max-width: @width-breakpoint-tablet ) {
+ // On mobile screens position the anchor arrow correctly so it points to the
+ // notifications badge.
+ .mw-echo-ui-overlay .oo-ui-popupWidget-anchor {
+ // Override the inline style.
+ left: auto !important;
+ right: 68px;
+ }
+}
+
.mw-echo-ui-overlay {
position: fixed;
bottom: 0;
@@ -23,4 +34,3 @@
top: auto !important;
}
}
-
diff --git a/Echo/modules/ui/mw.echo.ui.ActionMenuPopupWidget.js b/Echo/modules/ui/mw.echo.ui.ActionMenuPopupWidget.js
index 9d0a81de..c3e287a5 100644
--- a/Echo/modules/ui/mw.echo.ui.ActionMenuPopupWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.ActionMenuPopupWidget.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Action menu popup widget for echo items.
*
@@ -18,7 +18,8 @@
* @param {Object} [config] Configuration object
* @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
* for popups.
- * @cfg {number} [menuWidth=300] The width of the popup menu
+ * @cfg {Object} [horizontalPosition='auto'] How to position the menu, see OO.ui.FloatableElement.
+ * By default, 'start' will be tried first, and if that doesn't fit, 'end' will be used.
*/
mw.echo.ui.ActionMenuPopupWidget = function MwEchoUiActionMenuPopupWidget( config ) {
config = config || {};
@@ -28,14 +29,14 @@
this.$overlay = config.$overlay || this.$element;
- this.menuWidth = config.menuWidth || 300;
-
// Menu
- this.menu = new OO.ui.MenuSelectWidget( {
+ this.customMenuPosition = ( config.horizontalPosition || 'auto' ) !== 'auto';
+ this.menu = new OO.ui.MenuSelectWidget( $.extend( {
$floatableContainer: this.$element,
+ horizontalPosition: this.customMenuPosition ? config.horizontalPosition : 'start',
classes: [ 'mw-echo-ui-actionMenuPopupWidget-menu' ],
widget: this
- } );
+ } ) );
this.$overlay.append( this.menu.$element );
// Events
@@ -60,14 +61,14 @@
* @private
*/
mw.echo.ui.ActionMenuPopupWidget.prototype.onAction = function () {
+ // HACK: If config.horizontalPosition isn't set, first try 'start', then 'end'
+ if ( !this.customMenuPosition ) {
+ this.menu.setHorizontalPosition( 'start' );
+ }
this.menu.toggle();
- // HACK: The menu is attempting to be the same size as the container,
- // which in our case is not the point at all. We need the menu
- // to be larger, so force this setting:
- this.menu.$element.css( 'width', this.menuWidth );
- // HACK: Prevent ClippableElement from overwriting this width value on scroll
- // or window resize
- this.menu.toggleClipping( false );
+ if ( !this.customMenuPosition && this.menu.isClipped() ) {
+ this.menu.setHorizontalPosition( 'end' );
+ }
};
/**
@@ -86,4 +87,4 @@
mw.echo.ui.ActionMenuPopupWidget.prototype.getMenu = function () {
return this.menu;
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.BadgeLinkWidget.js b/Echo/modules/ui/mw.echo.ui.BadgeLinkWidget.js
index 9eb67e6e..ed1cf4c5 100644
--- a/Echo/modules/ui/mw.echo.ui.BadgeLinkWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.BadgeLinkWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Notification badge button widget for echo popup.
*
@@ -35,6 +35,13 @@
if ( config.href !== undefined && OO.ui.isSafeUrl( config.href ) ) {
this.$element.attr( 'href', config.href );
}
+ if ( this.type === 'alert' ) {
+ this.$element
+ .addClass( 'oo-ui-icon-bell' );
+ } else {
+ this.$element
+ .addClass( 'oo-ui-icon-tray' );
+ }
};
OO.inheritClass( mw.echo.ui.BadgeLinkWidget, OO.ui.Widget );
@@ -75,4 +82,4 @@
mw.hook( 'ext.echo.badge.countChange' ).fire( this.type, this.count, convertedNumber );
}
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.BundleNotificationItemWidget.js b/Echo/modules/ui/mw.echo.ui.BundleNotificationItemWidget.js
index bdd07e06..b3c4c9af 100644
--- a/Echo/modules/ui/mw.echo.ui.BundleNotificationItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.BundleNotificationItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Bundle notification item widget.
* This widget is expandable and displays
@@ -189,8 +189,11 @@
this.expanded = show !== undefined ? !!show : !this.expanded;
if ( show ) {
+ // FIXME: Use CSS transition
+ // eslint-disable-next-line no-jquery/no-slide
this.getList().$element.slideDown();
} else {
+ // eslint-disable-next-line no-jquery/no-slide
this.getList().$element.slideUp();
}
};
@@ -219,4 +222,4 @@
mw.echo.ui.BundleNotificationItemWidget.prototype.getList = function () {
return this.listWidget;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.ClonedNotificationItemWidget.js b/Echo/modules/ui/mw.echo.ui.ClonedNotificationItemWidget.js
index 37046c7e..057638db 100644
--- a/Echo/modules/ui/mw.echo.ui.ClonedNotificationItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.ClonedNotificationItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/* global moment:false */
/**
* A wrapper widget for a fake, cloned notification. This is used
@@ -84,4 +84,4 @@
mw.echo.ui.ClonedNotificationItemWidget.prototype.resetInitiallyUnseen = function () {
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.ConfirmationPopupWidget.js b/Echo/modules/ui/mw.echo.ui.ConfirmationPopupWidget.js
index e44354a8..6541dae4 100644
--- a/Echo/modules/ui/mw.echo.ui.ConfirmationPopupWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.ConfirmationPopupWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Confirmation overlay widget, especially for mobile display.
* The behavior of this widget is to appear with a given confirmation
@@ -56,6 +56,8 @@
* @private
*/
mw.echo.ui.ConfirmationPopupWidget.prototype.hide = function () {
+ // FIXME: Use CSS transition
+ // eslint-disable-next-line no-jquery/no-fade
this.$element.fadeOut();
};
@@ -68,4 +70,4 @@
mw.echo.ui.ConfirmationPopupWidget.prototype.setLabel = function ( label ) {
this.labelWidget.setLabel( label );
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.CrossWikiNotificationItemWidget.js b/Echo/modules/ui/mw.echo.ui.CrossWikiNotificationItemWidget.js
index d96a5a65..4936780e 100644
--- a/Echo/modules/ui/mw.echo.ui.CrossWikiNotificationItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.CrossWikiNotificationItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Cross-wiki notification item widget.
* This widget is expandable and displays groups of
@@ -311,8 +311,11 @@
this.$element.toggleClass( 'mw-echo-ui-crossWikiNotificationItemWidget-expanded', this.expanded );
if ( this.expanded ) {
+ // FIXME: Use CSS transition
+ // eslint-disable-next-line no-jquery/no-slide
this.getList().$element.slideDown();
} else {
+ // eslint-disable-next-line no-jquery/no-slide
this.getList().$element.slideUp();
}
};
@@ -327,8 +330,8 @@
this.expanded ?
mw.msg( 'notification-link-text-collapse-all' ) :
// Messages that appear here are:
- // notification-link-text-expand-alert-count
- // notification-link-text-expand-notice-count
+ // * notification-link-text-expand-alert-count
+ // * notification-link-text-expand-notice-count
mw.msg(
'notification-link-text-expand-' +
( type === 'message' ? 'notice' : type ) +
@@ -351,4 +354,4 @@
mw.echo.ui.CrossWikiNotificationItemWidget.prototype.getList = function () {
return this.listWidget;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js b/Echo/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js
index 02807150..812d8799 100644
--- a/Echo/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* A filter for cross-wiki unread notifications
*
@@ -170,4 +170,4 @@
this.setItemSelected( item );
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.DatedNotificationsWidget.js b/Echo/modules/ui/mw.echo.ui.DatedNotificationsWidget.js
index 09c5c5c9..f7f7627b 100644
--- a/Echo/modules/ui/mw.echo.ui.DatedNotificationsWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.DatedNotificationsWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* A notifications list organized and separated by dates
*
@@ -176,4 +176,4 @@
return count;
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.DatedSubGroupListWidget.js b/Echo/modules/ui/mw.echo.ui.DatedSubGroupListWidget.js
index 4cbbc08a..e56e7546 100644
--- a/Echo/modules/ui/mw.echo.ui.DatedSubGroupListWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.DatedSubGroupListWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/* global moment:false */
/**
* A sub group widget that displays notifications divided by dates.
@@ -62,4 +62,4 @@
/* Initialization */
OO.inheritClass( mw.echo.ui.DatedSubGroupListWidget, mw.echo.ui.SubGroupListWidget );
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.FooterNoticeWidget.js b/Echo/modules/ui/mw.echo.ui.FooterNoticeWidget.js
index 185c3aec..812df064 100644
--- a/Echo/modules/ui/mw.echo.ui.FooterNoticeWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.FooterNoticeWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Footer notice widget.
*
@@ -77,4 +77,4 @@
this.toggle( false );
this.emit( 'dismiss' );
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.MenuItemWidget.js b/Echo/modules/ui/mw.echo.ui.MenuItemWidget.js
index 2a7785e4..810d8ce4 100644
--- a/Echo/modules/ui/mw.echo.ui.MenuItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.MenuItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Secondary menu item
*
@@ -124,4 +124,4 @@
mw.echo.ui.MenuItemWidget.prototype.isDynamicAction = function () {
return this.dynamic;
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.NotificationBadgeWidget.js b/Echo/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
index a07fa118..50067145 100644
--- a/Echo/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Notification badge button widget for echo popup.
*
@@ -65,8 +65,8 @@
numItems: this.numItems,
flags: buttonFlags,
// The following messages can be used here:
- // tooltip-pt-notifications-alert
- // tooltip-pt-notifications-notice
+ // * tooltip-pt-notifications-alert
+ // * tooltip-pt-notifications-notice
title: mw.msg( 'tooltip-pt-notifications-' + adjustedTypeString ),
href: config.href
} );
@@ -89,6 +89,7 @@
href: links.notifications,
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer-allnotifs' ]
} );
+ allNotificationsButton.$element.children().first().removeAttr( 'role' );
preferencesButton = new OO.ui.ButtonWidget( {
icon: 'settings',
@@ -96,6 +97,7 @@
href: links.preferences,
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer-preferences' ]
} );
+ preferencesButton.$element.children().first().removeAttr( 'role' );
footerButtonGroupWidget = new OO.ui.ButtonGroupWidget( {
items: [ allNotificationsButton, preferencesButton ],
@@ -119,8 +121,8 @@
$autoCloseIgnore: this.$element.add( this.$menuOverlay ),
head: true,
// The following messages can be used here:
- // echo-notification-alert-text-only
- // echo-notification-notice-text-only
+ // * echo-notification-alert-text-only
+ // * echo-notification-notice-text-only
label: mw.msg(
'echo-notification-' + adjustedTypeString +
'-text-only'
@@ -165,8 +167,8 @@
this.$element
.prop( 'id', 'pt-notifications-' + adjustedTypeString )
// The following classes can be used here:
- // mw-echo-ui-notificationBadgeButtonPopupWidget-alert
- // mw-echo-ui-notificationBadgeButtonPopupWidget-message
+ // * mw-echo-ui-notificationBadgeButtonPopupWidget-alert
+ // * mw-echo-ui-notificationBadgeButtonPopupWidget-message
.addClass(
'mw-echo-ui-notificationBadgeButtonPopupWidget ' +
'mw-echo-ui-notificationBadgeButtonPopupWidget-' + adjustedTypeString
@@ -240,7 +242,6 @@
unreadCount = this.manager.getUnreadCounter().getCount();
cappedUnreadCount = this.manager.getUnreadCounter().getCappedNotificationCount( unreadCount );
- cappedUnreadCount = mw.language.convertNumber( cappedUnreadCount );
badgeLabel = mw.message( 'echo-badge-count', mw.language.convertNumber( cappedUnreadCount ) ).text();
this.badgeButton.setLabel( badgeLabel );
@@ -345,4 +346,4 @@
} );
this.hasRunFirstTime = true;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.NotificationItemWidget.js b/Echo/modules/ui/mw.echo.ui.NotificationItemWidget.js
index fce6fb52..a31f3d46 100644
--- a/Echo/modules/ui/mw.echo.ui.NotificationItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.NotificationItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/* global moment:false */
/**
* A base widget for displaying notification items.
@@ -47,7 +47,11 @@
if ( this.model.getIconURL() ) {
$icon = $( '<div>' )
.addClass( 'mw-echo-ui-notificationItemWidget-icon' )
- .append( $( '<img>' ).attr( 'src', this.model.getIconURL() ) );
+ .append( $( '<img>' ).attr( {
+ src: this.model.getIconURL(),
+ role: 'presentation',
+ alt: ' '
+ } ) );
}
// Content
@@ -73,7 +77,8 @@
// Actions menu
this.actionsButtonSelectWidget = new OO.ui.ButtonSelectWidget( {
- classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-buttons' ]
+ classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-buttons' ],
+ tabIndex: -1
} );
// Popup menu
@@ -81,7 +86,7 @@
framed: false,
icon: 'ellipsis',
$overlay: this.$overlay,
- menuWidth: 200,
+ horizontalPosition: this.bundle ? 'end' : 'auto',
title: mw.msg( 'echo-notification-more-options-tooltip' ),
classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-menu' ]
} );
@@ -157,6 +162,7 @@
// Limit to 2 items outside the menu
if ( isOutsideMenu ) {
this.actionsButtonSelectWidget.addItems( [ linkButton ] );
+ this.actionsButtonSelectWidget.setTabIndex( 0 );
outsideMenuItemCounter++;
} else {
this.menuPopupButtonWidget.getMenu().addItems( [ linkButton ] );
@@ -430,4 +436,4 @@
mw.echo.ui.NotificationItemWidget.prototype.isFake = function () {
return false;
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.NotificationsInboxWidget.js b/Echo/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
index 2c1a93e7..57e08328 100644
--- a/Echo/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* An inbox-type widget that encompases a dated notifications list with pagination
*
@@ -11,7 +11,6 @@
* @param {mw.echo.dm.ModelManager} manager Model manager
* @param {Object} [config] Configuration object
* @cfg {number} [limit=25] Limit the number of notifications per page
- * @cfg {string} [helpLink] Link to help page
* @cfg {string} [prefLink] Link to preferences page
* @cfg {jQuery} [$overlay] An overlay for the popup menus
*/
@@ -68,7 +67,6 @@
this.manager,
{
framed: true,
- helpLink: config.helpLink,
prefLink: config.prefLink,
$overlay: this.$overlay
}
@@ -323,4 +321,4 @@
this.datedListWidget.toggle( !displayMessage );
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js b/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js
index de08252e..c0f4e918 100644
--- a/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.NotificationsListWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Notifications list widget.
* All of its items must be of the mw.echo.ui.NotificationItem type.
@@ -243,4 +243,4 @@
itemWidgets[ i ].resetInitiallyUnseen();
}
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.NotificationsWrapper.js b/Echo/modules/ui/mw.echo.ui.NotificationsWrapper.js
index 45ceaa8d..ebf6b7c0 100644
--- a/Echo/modules/ui/mw.echo.ui.NotificationsWrapper.js
+++ b/Echo/modules/ui/mw.echo.ui.NotificationsWrapper.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Wrapper for the notifications widget, for view outside the popup.
*
@@ -82,4 +82,4 @@
widget.promiseRunning = false;
} );
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.PageFilterWidget.js b/Echo/modules/ui/mw.echo.ui.PageFilterWidget.js
index cfe89bea..3d7940eb 100644
--- a/Echo/modules/ui/mw.echo.ui.PageFilterWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.PageFilterWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* A widget that displays wikis and their pages to choose a filter
*
@@ -43,7 +43,8 @@
// Initialization
this.populateDataFromModel();
this.$element
- .addClass( 'mw-echo-ui-pageFilterWidget' );
+ .addClass( 'mw-echo-ui-pageFilterWidget' )
+ .attr( 'aria-label', mw.message( 'echo-specialpage-pagefilterwidget-aria-label' ).text() );
};
/* Initialization */
@@ -153,4 +154,4 @@
mw.echo.ui.PageFilterWidget.prototype.sortingFunction = function ( item, otherItem ) {
return Number( otherItem.getCount() ) - Number( item.getCount() );
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js b/Echo/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js
index d87f0106..21116106 100644
--- a/Echo/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* An option widget for the page filter in PageFilterWidget
*
@@ -104,4 +104,4 @@
return this;
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.PaginationWidget.js b/Echo/modules/ui/mw.echo.ui.PaginationWidget.js
index fdfedde7..2e498ae4 100644
--- a/Echo/modules/ui/mw.echo.ui.PaginationWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.PaginationWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* A pagination widget allowing the user to go forward, backwards,
* and after a couple of pages, go back to home.
@@ -127,7 +127,6 @@
this.labelWidget.toggle( !this.isDisabled() );
};
- // eslint-disable-next-line valid-jsdoc
/**
* Set the 'disabled' state of the widget.
*
@@ -176,4 +175,4 @@
this.labelWidget.setLabel( label );
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.PlaceholderItemWidget.js b/Echo/modules/ui/mw.echo.ui.PlaceholderItemWidget.js
index 31ab206d..9165e4c8 100644
--- a/Echo/modules/ui/mw.echo.ui.PlaceholderItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.PlaceholderItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Placeholder notification option widget for echo popup.
*
@@ -89,4 +89,4 @@
*/
mw.echo.ui.PlaceholderItemWidget.prototype.resetInitiallyUnseen = function () {};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.ReadStateButtonSelectWidget.js b/Echo/modules/ui/mw.echo.ui.ReadStateButtonSelectWidget.js
index 09a01bff..56a221bd 100644
--- a/Echo/modules/ui/mw.echo.ui.ReadStateButtonSelectWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.ReadStateButtonSelectWidget.js
@@ -1,4 +1,4 @@
-( function ( $, mw ) {
+( function () {
/**
* A select widget for notification read state: 'all', 'read' or 'unread'
*
@@ -59,4 +59,4 @@
this.emit( 'filter', data );
}
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.SingleNotificationItemWidget.js b/Echo/modules/ui/mw.echo.ui.SingleNotificationItemWidget.js
index e82ae161..405ccd18 100644
--- a/Echo/modules/ui/mw.echo.ui.SingleNotificationItemWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.SingleNotificationItemWidget.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Single notification item widget for echo popup.
*
@@ -98,4 +98,4 @@
this.toggleRead( this.model.isRead() );
this.toggleSeen( this.model.isSeen() );
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.SortedListWidget.js b/Echo/modules/ui/mw.echo.ui.SortedListWidget.js
index 13a4d808..4f1a5cd5 100644
--- a/Echo/modules/ui/mw.echo.ui.SortedListWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.SortedListWidget.js
@@ -1,4 +1,4 @@
-( function ( mw ) {
+( function () {
/**
* Sorted list widget. This is a group widget that sorts its items
* according to a given sorting callback.
@@ -83,10 +83,13 @@
this.addItems( fakeWidget );
// fade out fake
+ // FIXME: Use CSS transition
+ // eslint-disable-next-line no-jquery/no-fade
fakeWidget.$element.fadeOut( 400, function () {
// remove fake
widget.removeItems( fakeWidget );
// fade-in real item
+ // eslint-disable-next-line no-jquery/no-fade
item.$element.fadeIn( 400 );
} );
} else {
@@ -150,7 +153,6 @@
return null;
};
- // eslint-disable-next-line valid-jsdoc
/**
* Remove items.
*
@@ -241,7 +243,6 @@
}
};
- // eslint-disable-next-line valid-jsdoc
/**
* Clear all items
*
@@ -277,4 +278,4 @@
);
};
-}( mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.SpecialHelpMenuWidget.js b/Echo/modules/ui/mw.echo.ui.SpecialHelpMenuWidget.js
index d2ae205c..786c0150 100644
--- a/Echo/modules/ui/mw.echo.ui.SpecialHelpMenuWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.SpecialHelpMenuWidget.js
@@ -1,21 +1,20 @@
-( function ( $, mw ) {
+( function () {
/**
* Widget for the settings menu in the Special:Notifications page
*
* @param {mw.echo.dm.ModelManager} manager Model manager
* @param {Object} config Configuration object
- * @cfg {string} [helpLink] Link to help page
* @cfg {string} [prefLink] Link to preferences page
*/
mw.echo.ui.SpecialHelpMenuWidget = function MwEchoUiSpecialHelpMenuWidget( manager, config ) {
- var handle;
-
config = config || {};
// Parent constructor
mw.echo.ui.SpecialHelpMenuWidget.super.call( this, $.extend( {
- // Icon and indicator set on handle button instead
- indicator: '',
+ icon: 'settings',
+ label: mw.msg( 'echo-specialpage-special-help-menu-widget-aria-label' ),
+ indicator: 'down',
+ invisibleLabel: true,
menu: {
classes: [ 'mw-echo-ui-specialHelpMenuWidget-menu' ],
horizontalPosition: 'end',
@@ -23,15 +22,6 @@
}
}, config ) );
- // Replace handle with a button widget. Use this.$handle to preserve bindings.
- this.$handle.empty().attr( 'class', '' );
- handle = new OO.ui.ButtonWidget( {
- $element: this.$handle,
- icon: 'settings',
- indicator: 'down'
- } );
- this.$element.append( handle.$element );
-
this.manager = manager;
this.markAllReadOption = new OO.ui.MenuOptionWidget( {
@@ -55,19 +45,6 @@
] );
}
- if ( config.helpLink ) {
- this.menu.addItems( [
- // Help link
- new OO.ui.MenuOptionWidget( {
- // Use link for accessibility
- $element: $( '<a>' ).attr( 'href', config.helpLink ),
- icon: 'help',
- label: mw.msg( 'echo-learn-more' ),
- data: { href: config.helpLink }
- } )
- ] );
- }
-
// Events
this.manager.connect( this, {
localCountChange: 'onLocalCountChange'
@@ -80,7 +57,7 @@
/* Initialization */
- OO.inheritClass( mw.echo.ui.SpecialHelpMenuWidget, OO.ui.DropdownWidget );
+ OO.inheritClass( mw.echo.ui.SpecialHelpMenuWidget, OO.ui.ButtonMenuSelectWidget );
/* Events */
@@ -97,7 +74,6 @@
*/
mw.echo.ui.SpecialHelpMenuWidget.prototype.onSourcePageUpdate = function () {
this.markAllReadOption.setLabel( this.getMarkAllReadOptionLabel() );
-
};
/**
@@ -110,7 +86,7 @@
};
/**
- * Handle dropdown menu choose events
+ * Handle menu choose events
*
* @param {OO.ui.MenuOptionWidget} item Chosen item
*/
@@ -130,8 +106,6 @@
);
this.emit( 'markAllRead' );
}
- // Clear selection so handle doesn't change
- this.menu.selectItem();
};
/**
@@ -149,4 +123,4 @@
mw.msg( 'echo-mark-all-as-read' );
};
-}( jQuery, mediaWiki ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.SubGroupListWidget.js b/Echo/modules/ui/mw.echo.ui.SubGroupListWidget.js
index 0f9503ee..3ec51055 100644
--- a/Echo/modules/ui/mw.echo.ui.SubGroupListWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.SubGroupListWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* Sub group list widget.
* This widget contains a list of notifications from a single source
@@ -119,8 +119,9 @@
this.listWidget.$element
);
+ // eslint-disable-next-line no-jquery/no-global-selector
this.$pageContentText = $( '#mw-content-text' );
- $( window ).resize( this.resizeHeader.bind( this ) );
+ $( window ).on( 'resize', this.resizeHeader.bind( this ) );
// Resize the header after the stack finishes loading
// so the widget is attached
@@ -320,4 +321,4 @@
mw.echo.ui.SubGroupListWidget.prototype.getId = function () {
return this.model.getName();
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.ToggleReadCircleButtonWidget.js b/Echo/modules/ui/mw.echo.ui.ToggleReadCircleButtonWidget.js
index 6df684fe..9d32a10c 100644
--- a/Echo/modules/ui/mw.echo.ui.ToggleReadCircleButtonWidget.js
+++ b/Echo/modules/ui/mw.echo.ui.ToggleReadCircleButtonWidget.js
@@ -1,4 +1,4 @@
-( function ( mw, $ ) {
+( function () {
/**
* A button showing a circle that represents either 'mark as read' or 'mark as unread' states.
*
@@ -50,4 +50,4 @@
mw.msg( 'echo-notification-markasunread' )
);
};
-}( mediaWiki, jQuery ) );
+}() );
diff --git a/Echo/modules/ui/mw.echo.ui.js b/Echo/modules/ui/mw.echo.ui.js
index 12f39607..00f45316 100644
--- a/Echo/modules/ui/mw.echo.ui.js
+++ b/Echo/modules/ui/mw.echo.ui.js
@@ -1,7 +1,7 @@
-( function ( mw, $ ) {
+( function () {
mw.echo = mw.echo || {};
mw.echo.ui = {
$overlay: $( '<div>' )
.addClass( 'mw-echo-ui-overlay' )
};
-}( mediaWiki, jQuery ) );
+}() );