Mooeypoo has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/292600

Change subject: [wip] Add a cross-wiki sidebar to the Special:Notifications page
......................................................................

[wip] Add a cross-wiki sidebar to the Special:Notifications page

Change-Id: I57d827a47f80274d75364c2099a9624049a26834
---
M Resources.php
M i18n/en.json
M i18n/qqq.json
M modules/api/mw.echo.api.APIHandler.js
M modules/api/mw.echo.api.EchoApi.js
M modules/controller/mw.echo.Controller.js
M modules/echo.variables.less
M modules/model/mw.echo.dm.FiltersModel.js
A modules/model/mw.echo.dm.SourcePagesModel.js
M modules/special/ext.echo.special.js
A modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less
M modules/styles/mw.echo.ui.NotificationsInboxWidget.less
A modules/styles/mw.echo.ui.PageFilterWidget.less
A modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less
A modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js
M modules/ui/mw.echo.ui.DatedNotificationsWidget.js
A modules/ui/mw.echo.ui.FilteredNotificationsInboxWidget.js
M modules/ui/mw.echo.ui.NotificationsInboxWidget.js
A modules/ui/mw.echo.ui.PageFilterWidget.js
A modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js
20 files changed, 650 insertions(+), 43 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo 
refs/changes/00/292600/1

diff --git a/Resources.php b/Resources.php
index bca4c5c..9663fe0 100644
--- a/Resources.php
+++ b/Resources.php
@@ -163,6 +163,7 @@
                'scripts' => array(
                        'mw.echo.js',
                        'model/mw.echo.dm.js',
+                       'model/mw.echo.dm.SourcePagesModel.js',
                        'model/mw.echo.dm.PaginationModel.js',
                        'model/mw.echo.dm.FiltersModel.js',
                        'model/mw.echo.dm.ModelManager.js',
@@ -279,6 +280,9 @@
                        'ui/mw.echo.ui.DatedSubGroupListWidget.js',
                        'ui/mw.echo.ui.DatedNotificationsWidget.js',
                        'ui/mw.echo.ui.ReadStateButtonSelectWidget.js',
+                       'ui/mw.echo.ui.PageNotificationsOptionWidget.js',
+                       'ui/mw.echo.ui.PageFilterWidget.js',
+                       'ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js',
                        'ui/mw.echo.ui.NotificationsInboxWidget.js',
                        'special/ext.echo.special.js',
                ),
@@ -287,6 +291,9 @@
                        'styles/mw.echo.ui.DatedSubGroupListWidget.less',
                        'styles/mw.echo.ui.DatedNotificationsWidget.less',
                        'styles/mw.echo.ui.NotificationsInboxWidget.less',
+                       'styles/mw.echo.ui.PageNotificationsOptionWidget.less',
+                       'styles/mw.echo.ui.PageFilterWidget.less',
+                       'styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less',
                ),
                'dependencies' => array(
                        'ext.echo.ui',
@@ -300,6 +307,8 @@
                        'echo-notification-placeholder-filters',
                        'echo-specialpage-pagination-numnotifications',
                        'echo-specialpage-pagination-range',
+                       'echo-specialpage-pagefilters-title',
+                       'echo-specialpage-pagefilters-subtitle',
                        'echo-more-info',
                        'echo-feedback',
                        'echo-specialpage-section-markread',
diff --git a/i18n/en.json b/i18n/en.json
index 1c5db68..393b24f 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -99,6 +99,8 @@
        "echo-specialpage-markasread-invalid-id": "Invalid event ID",
        "echo-specialpage-pagination-numnotifications": "$1 
{{PLURAL:$1|notification|notifications}}",
        "echo-specialpage-pagination-range": "$1 - $2",
+       "echo-specialpage-pagefilters-title": "Recent activity",
+       "echo-specialpage-pagefilters-subtitle": "Pages with unread 
notifications",
        "notificationsmarkread-legend": "Mark notification as read",
        "echo-anon": "To receive notifications, [$1 create an account] or [$2 
log in].",
        "echo-none": "You have no notifications.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 9b792f1..e3bd031 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -90,6 +90,8 @@
        "echo-specialpage-markasread-invalid-id": "Error message shown to users 
who try to mark a notification as read with an invalid event ID.",
        "echo-specialpage-pagination-numnotifications": "Label noting the 
number of notifications displayed in the page. This only appears if there is a 
single page of results.\n\nParameters:\n* $1 - Number of notifications in the 
page.",
        "echo-specialpage-pagination-range": "Label noting the range of the 
notifications displayed in the page. This only appears if there are multiple 
pages of results available.\n\nParameters:\n* $1 - Number of the first item.\n* 
$2 - Number of the last item.",
+       "echo-specialpage-pagefilters-title": "Title of the page filter box in 
Special:Notifications page.",
+       "echo-specialpage-pagefilters-subtitle": "Subtitle of the page filter 
box in Special:Notifications page.",
        "notificationsmarkread-legend": "Title for the form that marks a 
notification as read in [[Special:NotificationsMarkAsRead]]",
        "echo-anon": "Error message shown to users who try to visit 
[[Special:Notifications]] as an anon.\n\nParameters:\n* $1 - URL of signup 
page, with returnto pointing to Special:Notifications\n* $2 - URL of login 
page, with returnto pointing to Special:Notifications",
        "echo-none": "Message shown to users who have no notifications. Also 
shown in the overlay.",
diff --git a/modules/api/mw.echo.api.APIHandler.js 
b/modules/api/mw.echo.api.APIHandler.js
index dce0d6d..bd26239 100644
--- a/modules/api/mw.echo.api.APIHandler.js
+++ b/modules/api/mw.echo.api.APIHandler.js
@@ -57,6 +57,29 @@
        mw.echo.api.APIHandler.prototype.fetchNotifications = null;
 
        /**
+        * Fetch all pages with unread notifications in them per wiki
+        *
+        * @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
+        */
+       mw.echo.api.APIHandler.prototype.fetchUnreadNotificationPages = 
function ( sources ) {
+               var params = {
+                               action: 'query',
+                               meta: 'unreadnotificationpages'
+                       };
+
+               if ( !sources || sources === '*' ) {
+                       params.unpwikis = '*';
+               } else {
+                       sources = Array.isArray( sources ) ? sources : [ 
sources ];
+                       params.unpwikis = sources.join( '|' );
+               }
+
+               return this.api.get( params );
+       };
+       /**
         * Create a new fetchNotifications promise that queries the API and 
overrides
         * the cached promise.
         *
diff --git a/modules/api/mw.echo.api.EchoApi.js 
b/modules/api/mw.echo.api.EchoApi.js
index bd5ad6d..c8b2ce0 100644
--- a/modules/api/mw.echo.api.EchoApi.js
+++ b/modules/api/mw.echo.api.EchoApi.js
@@ -46,6 +46,20 @@
        };
 
        /**
+        * Fetch all pages with unread notifications in them per wiki
+        *
+        * @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
+        */
+       mw.echo.api.EchoApi.prototype.fetchUnreadNotificationPages = function ( 
sources ) {
+               return this.network.getApiHandler( 'local' 
).fetchUnreadNotificationPages( sources )
+                       .then( function ( data ) {
+                               return OO.getProp( data, 'query', 
'unreadnotificationpages' );
+                       } );
+       };
+
+       /**
         * Fetch notifications from the server based on type
         *
         * @param {string} type Notification type to fetch: 'alert', 'message', 
or 'all'
diff --git a/modules/controller/mw.echo.Controller.js 
b/modules/controller/mw.echo.Controller.js
index bfd2081..7a8befb 100644
--- a/modules/controller/mw.echo.Controller.js
+++ b/modules/controller/mw.echo.Controller.js
@@ -62,6 +62,34 @@
        };
 
        /**
+        * Fetch unread pages in all wikis and create foreign API sources
+        * as needed.
+        *
+        * @return {jQuery.Promise} A promise that resolves when the page filter
+        *  model is updated with the unread notification count per page per 
wiki
+        */
+       mw.echo.Controller.prototype.fetchUnreadPagesByWiki = function () {
+               var controller = this,
+                       filterModel = this.manager.getFiltersModel();
+
+               return this.api.fetchUnreadNotificationPages()
+                       .then( function ( data ) {
+                               var source,
+                                       foreignSources = {};
+
+                               // Register pages
+                               filterModel.setSourcePageFilterData( data );
+
+                               for ( source in data ) {
+                                       // Collect sources for API
+                                       foreignSources[ source ] = data[ source 
].source;
+                               }
+
+                               // Register the foreign sources in the API
+                               controller.api.registerForeignSources( 
foreignSources );
+                       } );
+       };
+       /**
         * Fetch notifications from the local API and sort them by date.
         * This method ignores cross-wiki notifications and bundles.
         *
diff --git a/modules/echo.variables.less b/modules/echo.variables.less
index db1d2da..dcb9551 100644
--- a/modules/echo.variables.less
+++ b/modules/echo.variables.less
@@ -16,3 +16,11 @@
 @opacity-mid: 0.8;
 
 @specialpage-separation-unit: 0.7em;
+@specialpage-sidebar-width: 20em;
+
+@grey-light: #777;
+@grey-medium: #555;
+@grey-dark: #333;
+@grey-darkest: #000;
+
+@border-color: #ccc;
diff --git a/modules/model/mw.echo.dm.FiltersModel.js 
b/modules/model/mw.echo.dm.FiltersModel.js
index 656d255..f5011ab 100644
--- a/modules/model/mw.echo.dm.FiltersModel.js
+++ b/modules/model/mw.echo.dm.FiltersModel.js
@@ -20,6 +20,8 @@
                if ( config.readState ) {
                        this.setReadState( config.readState );
                }
+
+               this.sourcePageModel = new mw.echo.dm.SourcePagesModel();
        };
 
        /* Initialization */
@@ -38,6 +40,58 @@
        /* Methods */
 
        /**
+        * Set the source page filter data
+        *
+        * @param {Object} apiObject A detailed object about sources and pages
+        *  from the API response.
+        * @fires update
+        */
+       mw.echo.dm.FiltersModel.prototype.setSourcePageFilterData = function ( 
apiObject ) {
+               this.sourcePageModel.setAllSources( apiObject );
+               this.emit( 'update' );
+       };
+
+       /**
+        * Get an array of all source names
+        *
+        * @return {string[]} Source names
+        */
+       mw.echo.dm.FiltersModel.prototype.getSourcesArray = function () {
+               return this.sourcePageModel.getSourcesArray();
+       };
+
+       /**
+        * Get a specific source's title
+        *
+        * @param {string} source Symbolic name for source
+        * @return {string} Source title
+        */
+       mw.echo.dm.FiltersModel.prototype.getSourceTitle = function ( source ) {
+               return this.sourcePageModel.getSourceTitle( source );
+       };
+
+       /**
+        * Get the total count of a source. This sums the count of all
+        * sub pages in that source.
+        *
+        * @param {string} source Symbolic name for source
+        * @return {number} Total count
+        */
+       mw.echo.dm.FiltersModel.prototype.getSourceTotalCount = function ( 
source ) {
+               return this.sourcePageModel.getSourceTotalCount( source );
+       };
+
+       /**
+        * Get a specific source's page definitions
+        *
+        * @param {string} source Symbolic name for source
+        * @return {Object} Page definition
+        */
+       mw.echo.dm.FiltersModel.prototype.getSourcePages = function ( source ) {
+               return this.sourcePageModel.getSourcePages( source );
+       };
+
+       /**
         * Set the read state filter
         *
         * @param {string} readState Notifications read state
diff --git a/modules/model/mw.echo.dm.SourcePagesModel.js 
b/modules/model/mw.echo.dm.SourcePagesModel.js
new file mode 100644
index 0000000..53337ea
--- /dev/null
+++ b/modules/model/mw.echo.dm.SourcePagesModel.js
@@ -0,0 +1,97 @@
+( function ( mw ) {
+       /**
+        * Source pages model for notification filtering
+        *
+        * @class
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        */
+       mw.echo.dm.SourcePagesModel = function MwEchoDmSourcePageModel( config 
) {
+               config = config || {};
+
+               this.sources = {};
+       };
+
+       /* Initialization */
+       OO.initClass( mw.echo.dm.SourcePagesModel );
+
+       /**
+        * Set all sources and pages. This will also reset and override any
+        * previously set information.
+        *
+        * @param {Object} apiObject A detailed object about sources and pages
+        *  from the API response.
+        */
+       mw.echo.dm.SourcePagesModel.prototype.setAllSources = function ( 
apiObject ) {
+               var source;
+
+               this.reset();
+               for ( source in apiObject ) {
+                       this.setSourcePagesDetails( source, apiObject[ source ] 
);
+               }
+       };
+
+       /**
+        * Get an array of all source names
+        *
+        * @return {string[]} Array of source names
+        */
+       mw.echo.dm.SourcePagesModel.prototype.getSourcesArray = function () {
+               return Object.keys( this.sources );
+       };
+
+       mw.echo.dm.SourcePagesModel.prototype.getSourceTitle = function ( 
source ) {
+               return this.sources[ source ] && this.sources[ source ].title;
+       };
+
+       mw.echo.dm.SourcePagesModel.prototype.getSourceTotalCount = function ( 
source ) {
+               return ( this.sources[ source ] && this.sources[ source 
].totalCount ) || '0';
+       };
+
+       mw.echo.dm.SourcePagesModel.prototype.getSourcePages = function ( 
source ) {
+               return this.sources[ source ] && this.sources[ source ].pages;
+       };
+
+       /**
+        * Reset the data
+        */
+       mw.echo.dm.SourcePagesModel.prototype.reset = function () {
+               this.sources = {};
+       };
+
+       /**
+        * Set the details of a source and its page definitions
+        *
+        * @private
+        * @param {string} source Source symbolic name
+        * @param {Object} details Details object
+        */
+       mw.echo.dm.SourcePagesModel.prototype.setSourcePagesDetails = function 
( source, details ) {
+               var id, pageDetails, count;
+
+               // Source information
+               this.sources[ source ] = {
+                       title: details.source.title,
+                       base: details.source.base,
+                       totalCount: 0,
+                       pages: {}
+               };
+
+               // Fill in pages
+               count = 0;
+               for ( id in details.pages ) {
+                       pageDetails = details.pages[ id ];
+                       this.sources[ source ].pages[ pageDetails.title ] = {
+                               title: pageDetails.title,
+                               count: pageDetails.count,
+                               id: id
+                       };
+
+                       count += parseInt( pageDetails.count );
+               }
+
+               // Update total count
+               this.sources[ source ].totalCount = count;
+       };
+} )( mediaWiki );
diff --git a/modules/special/ext.echo.special.js 
b/modules/special/ext.echo.special.js
index e45312d..8946393 100644
--- a/modules/special/ext.echo.special.js
+++ b/modules/special/ext.echo.special.js
@@ -28,6 +28,8 @@
                // Overlay
                $( 'body' ).append( mw.echo.ui.$overlay );
 
+               controller.fetchUnreadPagesByWiki();
+
                $content.empty().append( specialPageContainer.$element );
        } );
 } )( jQuery, mediaWiki );
diff --git a/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less 
b/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less
new file mode 100644
index 0000000..f6c6fdc
--- /dev/null
+++ b/modules/styles/mw.echo.ui.CrossWikiUnreadFilterWidget.less
@@ -0,0 +1,16 @@
+@import '../echo.variables';
+
+.mw-echo-ui-crossWikiUnreadFilterWidget {
+       border: 1px solid @border-color;
+       padding: @specialpage-separation-unit;
+       width: @specialpage-sidebar-width;
+
+       &-title {
+               font-size: 1.3em;
+               font-weight: bold;
+       }
+
+       &-subtitle {
+               color: @grey-light;
+       }
+}
diff --git a/modules/styles/mw.echo.ui.NotificationsInboxWidget.less 
b/modules/styles/mw.echo.ui.NotificationsInboxWidget.less
index da0683e..f5388eb 100644
--- a/modules/styles/mw.echo.ui.NotificationsInboxWidget.less
+++ b/modules/styles/mw.echo.ui.NotificationsInboxWidget.less
@@ -1,31 +1,42 @@
 @import '../echo.variables';
 .mw-echo-ui-notificationsInboxWidget {
-       &-toolbar {
-               &-row {
-                       display: table-row;
-               }
-               &-cell {
+       &-row {
+               display: table-row;
+               width: 100%;
+       }
+       &-cell {
+               display: table-cell;
+               vertical-align: middle;
+               &-placeholder {
                        display: table-cell;
-                       vertical-align: middle;
-               }
-
-               &-top {
-                       display: table;
-                       margin-bottom: 3 * @specialpage-separation-unit;
-
-
-                       &-placeholder {
-                               display: table-cell;
-                               width: 100%;
-                       }
-               }
-
-               &-bottom {
-                       display: table;
-                       width: inherit;
-                       margin-left: auto;
-                       margin-right: auto;
-                       margin-top: 3 * @specialpage-separation-unit;
+                       width: 100%;
                }
        }
+
+       &-sidebar {
+               width: @specialpage-sidebar-width;
+               padding-right: 1em;
+               vertical-align: top;
+       }
+
+       &-main {
+               vertical-align: top;
+
+               &-toolbar {
+                       &-top {
+                               display: table;
+                               margin-bottom: 3 * @specialpage-separation-unit;
+                               width: 100%;
+                       }
+
+                       &-bottom {
+                               display: table;
+                               width: inherit;
+                               margin-left: auto;
+                               margin-right: auto;
+                               margin-top: 3 * @specialpage-separation-unit;
+                       }
+               }
+       }
+
 }
diff --git a/modules/styles/mw.echo.ui.PageFilterWidget.less 
b/modules/styles/mw.echo.ui.PageFilterWidget.less
new file mode 100644
index 0000000..8f22f27
--- /dev/null
+++ b/modules/styles/mw.echo.ui.PageFilterWidget.less
@@ -0,0 +1,8 @@
+@import '../echo.variables';
+.mw-echo-ui-pageFilterWidget {
+       margin-top: 2 * @specialpage-separation-unit;
+
+       &-title {
+               font-weight: bold;
+       }
+}
diff --git a/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less 
b/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less
new file mode 100644
index 0000000..15b7338
--- /dev/null
+++ b/modules/styles/mw.echo.ui.PageNotificationsOptionWidget.less
@@ -0,0 +1,31 @@
+@import '../echo.variables';
+
+.mw-echo-ui-pageNotificationsOptionWidget {
+       width: 100%;
+       box-sizing: border-box;
+
+       &-icon {
+               float: left;
+               .oo-ui-iconElement-icon {
+                       display: inline-block;
+               }
+       }
+
+       &-title {
+               padding: 0.2em 0;
+       }
+
+       &-count {
+               float: right;
+               padding: 0.2em 0.5em;
+               margin-left: 0.5em;
+               background-color: #eee;
+               border-radius: 2px;
+               color: @grey-medium;
+
+               .oo-ui-optionWidget-selected & {
+                       background-color: #bbb;
+               }
+       }
+
+}
diff --git a/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js 
b/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js
new file mode 100644
index 0000000..77a9eff
--- /dev/null
+++ b/modules/ui/mw.echo.ui.CrossWikiUnreadFilterWidget.js
@@ -0,0 +1,112 @@
+( function ( $, mw ) {
+       /**
+        * A filter for cross-wiki unread notifications
+        *
+        * @class
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {mw.echo.Controller} controller Echo controller
+        * @param {mw.echo.dm.ModelManager} manager Model manager
+        * @param {Object} [config] Configuration object
+        */
+       mw.echo.ui.CrossWikiUnreadFilterWidget = function 
MwEchoUiCrossWikiUnreadFilterWidget( controller, manager, config ) {
+               var titleWidget, subtitleWidget;
+
+               config = config || {};
+
+               // Parent
+               mw.echo.ui.CrossWikiUnreadFilterWidget.parent.call( this, 
config );
+               // Mixin
+               OO.ui.mixin.GroupElement.call( this, config );
+
+               this.controller = controller;
+               this.manager = manager;
+               this.previousPageSelected = null;
+
+               titleWidget = new OO.ui.LabelWidget( {
+                       classes: [ 
'mw-echo-ui-crossWikiUnreadFilterWidget-title' ],
+                       label: mw.msg( 'echo-specialpage-pagefilters-title' )
+               } );
+               subtitleWidget = new OO.ui.LabelWidget( {
+                       classes: [ 
'mw-echo-ui-crossWikiUnreadFilterWidget-subtitle' ],
+                       label: mw.msg( 'echo-specialpage-pagefilters-subtitle' )
+               } );
+
+               // Events
+               this.manager.connect( this, { update: 'populateDataFromModel' } 
);
+               this.aggregate( { choose: 'pageFilterChoose' } );
+               this.connect( this, { pageFilterChoose: 'onPageFilterChoose' } 
);
+               // Always have a local wiki
+               this.localSource = new mw.echo.ui.PageFilterWidget(
+                       this.manager.getFiltersModel(),
+                       mw.config.get( 'wgDBname' )
+               );
+
+               this.$element
+                       .addClass( 'mw-echo-ui-crossWikiUnreadFilterWidget' )
+                       .append(
+                               titleWidget.$element,
+                               subtitleWidget.$element,
+                               this.$group
+                       );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.echo.ui.CrossWikiUnreadFilterWidget, OO.ui.Widget );
+       // Need to mixin base class as well
+       OO.mixinClass( mw.echo.ui.CrossWikiUnreadFilterWidget, 
OO.ui.mixin.GroupElement );
+       OO.mixinClass( mw.echo.ui.CrossWikiUnreadFilterWidget, 
OO.ui.mixin.GroupWidget );
+
+       /**
+        * Respond to choose event in one of the page filter widgets
+        *
+        * @param {mw.echo.ui.PageFilterWidget} widget The widget the event 
originated from
+        * @param {mw.echo.ui.PageNotificationsOptionWidget} item The chosen 
item
+        * @fires filter
+        */
+       mw.echo.ui.CrossWikiUnreadFilterWidget.prototype.onPageFilterChoose = 
function ( widget, item ) {
+               var data = item && item.getData();
+
+               if ( data ) {
+                       // Unselect the previous item
+                       if ( this.previousPageSelected ) {
+                               this.previousPageSelected.setSelected( false );
+                       }
+                       this.previousPageSelected = item;
+
+                       // Emit a choice
+                       this.emit( 'filter', data );
+               }
+       };
+
+       mw.echo.ui.CrossWikiUnreadFilterWidget.prototype.populateDataFromModel 
= function () {
+               var i, source, widget,
+                       widgets = [],
+                       filtersModel = this.manager.getFiltersModel(),
+                       sources = filtersModel.getSourcesArray();
+
+               for ( i = 0; i < sources.length; i++ ) {
+                       source = sources[ i ];
+                       widget = new mw.echo.ui.PageFilterWidget(
+                               filtersModel,
+                               source,
+                               {
+                                       title: filtersModel.getSourceTitle( 
source ),
+                                       unreadCount: 
filtersModel.getSourceTotalCount( source )
+                               }
+                       );
+                       widgets.push( widget );
+                       if ( source !== mw.config.get( 'wgDBname' ) ) {
+                               this.localSource = widget;
+                       }
+               }
+
+               this.clearItems();
+               this.addItems( widgets );
+               // Local source is always on top
+               // this.addItems( [ this.localSource ], 0 );
+       };
+
+} )( jQuery, mediaWiki );
diff --git a/modules/ui/mw.echo.ui.DatedNotificationsWidget.js 
b/modules/ui/mw.echo.ui.DatedNotificationsWidget.js
index b61efdd..0755cad 100644
--- a/modules/ui/mw.echo.ui.DatedNotificationsWidget.js
+++ b/modules/ui/mw.echo.ui.DatedNotificationsWidget.js
@@ -73,16 +73,10 @@
         * Respond to model manager update event.
         * This event means we are repopulating the entire list and the
         * associated models within it.
-        *
-        * @param {Object} [models] Object of new models to populate the
-        *  list. If not given, the method will request all models from the
-        *  manager.
         */
        mw.echo.ui.DatedNotificationsWidget.prototype.populateFromModel = 
function ( models ) {
                var modelId, model, subgroupWidget,
                        groupWidgets = [];
-
-               models = models || this.manager.getAllNotificationModels();
 
                // Detach all attached models
                for ( modelId in this.models ) {
diff --git a/modules/ui/mw.echo.ui.FilteredNotificationsInboxWidget.js 
b/modules/ui/mw.echo.ui.FilteredNotificationsInboxWidget.js
new file mode 100644
index 0000000..163eb8c
--- /dev/null
+++ b/modules/ui/mw.echo.ui.FilteredNotificationsInboxWidget.js
@@ -0,0 +1,41 @@
+( function ( $, mw ) {
+       /**
+        * A filtered inbox widget including a filters sidebar and an inbox view
+        * of dated notifications list.
+        *
+        * @class
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {mw.echo.Controller} controller Echo controller
+        * @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 {jQuery} [$overlay] An overlay for the popup menus
+        */
+       mw.echo.ui.FilteredNotificationsInboxWidget = function 
MwEchoUiFilteredNotificationsInboxWidget( controller, manager, config ) {
+               config = config || {};
+
+               // Parent
+               mw.echo.ui.FilteredNotificationsInboxWidget.parent.call( this, 
config );
+
+               this.controller = controller;
+               this.manager = manager;
+
+               this.inboxWidget = new mw.echo.ui.NotificationsInboxWidget(
+                       this.controller,
+                       this.manager,
+                       {
+                               limit: config.limit,
+                               $overlay: config.$overlay
+                       }
+               );
+
+               this.$element
+                       .addClass( 
'mw-echo-ui-filteredNotificationsInboxWidget' );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.echo.ui.NotificationsInboxWidget, OO.ui.Widget );
+} )( jQuery, mediaWiki );
diff --git a/modules/ui/mw.echo.ui.NotificationsInboxWidget.js 
b/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
index 93bded3..b4ed497 100644
--- a/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
+++ b/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
@@ -14,6 +14,8 @@
         * @cfg {jQuery} [$overlay] An overlay for the popup menus
         */
        mw.echo.ui.NotificationsInboxWidget = function 
MwEchoUiNotificationsInboxWidget( controller, manager, config ) {
+               var $main, $sidebar;
+
                config = config || {};
 
                // Parent
@@ -59,6 +61,12 @@
                // Filter by read state
                this.readStateSelectWidget = new 
mw.echo.ui.ReadStateButtonSelectWidget();
 
+               // Sidebar filters
+               this.xwikiUnreadWidget = new 
mw.echo.ui.CrossWikiUnreadFilterWidget(
+                       this.controller,
+                       this.manager
+               );
+
                // Events
                this.readStateSelectWidget.connect( this, { filter: 
'onReadStateFilter' } );
                this.manager.getFiltersModel().connect( this, { update: 
'updateReadStateSelectWidget' } );
@@ -69,37 +77,41 @@
                this.bottomPaginationWidget.setDisabled( true );
 
                // Initialization
-               this.$element
-                       .addClass( 'mw-echo-ui-notificationsInboxWidget' )
+               $sidebar = $( '<div>' )
+                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-sidebar' )
+                       .append( this.xwikiUnreadWidget.$element );
+
+               $main = $( '<div>' )
+                       .addClass( 'mw-echo-ui-notificationsInboxWidget-main' )
                        .append(
                                $( '<div>' )
-                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-toolbar-top' )
+                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-main-toolbar-top' )
                                        .append(
                                                $( '<div>' )
-                                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-toolbar-row' )
+                                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-row' )
                                                        .append(
                                                                $( '<div>' )
-                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbar-readState' )
-                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbar-cell' )
+                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-readState' )
+                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
                                                                        
.append( this.readStateSelectWidget.$element ),
                                                                $( '<div>' )
-                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbar-top-placeholder' ),
+                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-cell-placeholder' ),
                                                                $( '<div>' )
-                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbar-pagination' )
-                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbar-cell' )
+                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-pagination' )
+                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
                                                                        
.append( this.topPaginationWidget.$element )
                                                        )
                                        ),
                                this.noticeMessageWidget.$element,
                                this.datedListWidget.$element,
                                $( '<div>' )
-                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-toolbar-bottom' )
+                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-main-toolbar-bottom' )
                                        .append(
                                                $( '<div>' )
-                                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-toolbar-row' )
+                                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-row' )
                                                        .append(
                                                                $( '<div>' )
-                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbar-cell' )
+                                                                       
.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
                                                                        .append(
                                                                                
this.bottomPaginationWidget.$element
                                                                        )
@@ -107,6 +119,19 @@
                                        )
                        );
 
+               this.$element
+                       .addClass( 'mw-echo-ui-notificationsInboxWidget' )
+                       .append(
+                               $( '<div>' )
+                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-row' )
+                                       .append(
+                                               $sidebar
+                                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-cell' ),
+                                               $main
+                                                       .addClass( 
'mw-echo-ui-notificationsInboxWidget-cell' )
+                                       )
+                       );
+
                this.updateReadStateSelectWidget();
                this.populateNotifications();
        };
diff --git a/modules/ui/mw.echo.ui.PageFilterWidget.js 
b/modules/ui/mw.echo.ui.PageFilterWidget.js
new file mode 100644
index 0000000..cc4d27e
--- /dev/null
+++ b/modules/ui/mw.echo.ui.PageFilterWidget.js
@@ -0,0 +1,71 @@
+( function ( $, mw ) {
+       /**
+        * A widget that displays wikis and their pages to choose a filter
+        *
+        * @class
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {mw.echo.dm.FiltersModel} filterModel Filters model
+        * @param {string} source Symbolic name for the source
+        * @param {Object} [config] Configuration object
+        * @cfg {string} [title] The title of this page group, usually
+        *  the name of the wiki that the pages belong to
+        * @cfg {number} [unreadCount] Number of unread notifications
+        */
+       mw.echo.ui.PageFilterWidget = function MwEchoUiPageFilterWidget( 
filterModel, source, config ) {
+               config = config || {};
+
+               // Parent
+               mw.echo.ui.PageFilterWidget.parent.call( this, config );
+
+               this.model = filterModel;
+               this.source = source;
+
+               // Title option
+               this.title = new mw.echo.ui.PageNotificationsOptionWidget( {
+                       label: config.title,
+                       unreadCount: config.unreadCount || 
this.model.getSourceTotalCount( this.source ),
+                       data: 'all',
+                       classes: [ 'mw-echo-ui-pageFilterWidget-title' ]
+               } );
+
+               // Initialization
+               this.populateDataFromModel();
+               this.$element
+                       .addClass( 'mw-echo-ui-pageFilterWidget' );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.echo.ui.PageFilterWidget, OO.ui.SelectWidget );
+
+       mw.echo.ui.PageFilterWidget.prototype.setTotalCount = function ( count 
) {
+               this.title.setCount( count );
+       };
+
+       mw.echo.ui.PageFilterWidget.prototype.populateDataFromModel = function 
() {
+               var page,
+                       optionWidgets = [],
+                       sourcePages = this.model.getSourcePages( this.source );
+
+               for ( page in sourcePages ) {
+                       optionWidgets.push(
+                               new mw.echo.ui.PageNotificationsOptionWidget( {
+                                       label: sourcePages[ page ].title,
+                                       // TODO: Pages that are a user page 
should
+                                       // have a user icon
+                                       icon: 'article',
+                                       unreadCount: sourcePages[ page ].count,
+                                       data: sourcePages[ page ].id,
+                                       classes: [ 
'mw-echo-ui-pageFilterWidget-page' ]
+                               } )
+                       );
+               }
+
+               this.clearItems();
+               this.addItems( optionWidgets );
+               // Title is always on top
+               this.addItems( [ this.title ], 0 );
+       };
+} )( jQuery, mediaWiki );
diff --git a/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js 
b/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js
new file mode 100644
index 0000000..80d0e4c
--- /dev/null
+++ b/modules/ui/mw.echo.ui.PageNotificationsOptionWidget.js
@@ -0,0 +1,59 @@
+( function ( $, mw ) {
+       /**
+        * An option widget for the page filter in PageFilterWidget
+        *
+        * @class
+        * @extends OO.ui.OptionWidget
+        * @mixins OO.ui.mixin.IconElement
+        *
+        * @constructor
+        * @param {Object} [config] Configuration object
+        * @cfg {number} [unreadCount] Number of unread notifications
+        * @cfg {string} [icon] Symbolic name of an OOjs-UI icon to display
+        */
+       mw.echo.ui.PageNotificationsOptionWidget = function 
MwEchoUiPageNotificationsOptionWidget( config ) {
+               config = config || {};
+
+               // Parent
+               mw.echo.ui.PageNotificationsOptionWidget.parent.call( this, 
config );
+               // Mixin constructors
+               OO.ui.mixin.IconElement.call( this, config );
+
+               this.$label
+                       .addClass( 
'mw-echo-ui-pageNotificationsOptionWidget-title-label' );
+
+               this.unreadCountLabel = new OO.ui.LabelWidget( {
+                       classes: [ 
'mw-echo-ui-pageNotificationsOptionWidget-label-count' ],
+                       label: String( config.unreadCount )
+               } );
+
+               // Initialization
+               this.$element
+                       .addClass( 'mw-echo-ui-pageNotificationsOptionWidget' )
+                       .append(
+                               $( '<div>' )
+                                       .addClass( 
'mw-echo-ui-pageNotificationsOptionWidget-count' )
+                                       .append( this.unreadCountLabel.$element 
),
+                               $( '<div>' )
+                                       .addClass( 
'mw-echo-ui-pageNotificationsOptionWidget-title' )
+                                       .append( this.$label )
+                       );
+
+               if ( config.icon ) {
+                       this.$element.prepend(
+                               $( '<div>' )
+                                       .addClass( 
'mw-echo-ui-pageNotificationsOptionWidget-icon' )
+                                       .append( this.$icon )
+                       );
+               }
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.echo.ui.PageNotificationsOptionWidget, 
OO.ui.OptionWidget );
+       OO.mixinClass( mw.echo.ui.PageNotificationsOptionWidget, 
OO.ui.mixin.IconElement );
+
+       mw.echo.ui.PageNotificationsOptionWidget.prototype.setCount = function 
( count ) {
+               this.unreadCountLabel.setLabel( count );
+       };
+} )( jQuery, mediaWiki );

-- 
To view, visit https://gerrit.wikimedia.org/r/292600
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I57d827a47f80274d75364c2099a9624049a26834
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <mor...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to