Mooeypoo has uploaded a new change for review.

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

Change subject: [super-atomic-WIP] Adding filters to RecentChanges
......................................................................

[super-atomic-WIP] Adding filters to RecentChanges

This is a super super super WIP patch, not ready to be reviewed
or considered. Don't look at it. Seriously, avert your eyes.

Go ahead.

Do it.

You're still looking.

Stop.

Bug: T149435
Bug: T149452
Bug: T144448
Change-Id: Ic545ff1462998b610d7edae59472ecce2e7d51ea
---
M includes/specials/SpecialRecentchanges.php
M resources/Resources.php
A resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js
A resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
A resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
A resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
A resources/src/mediawiki.rcfilters/mw.rcfilters.js
A resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
A resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js
A resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js
A resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js
A resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js
12 files changed, 498 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/32/320332/1

diff --git a/includes/specials/SpecialRecentchanges.php 
b/includes/specials/SpecialRecentchanges.php
index cd3299c..6e6e9e3 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -521,6 +521,8 @@
                parent::addModules();
                $out = $this->getOutput();
                $out->addModules( 'mediawiki.special.recentchanges' );
+               // TODO: Add a config option / feature flag
+               $out->addModules( 'mediawiki.rcfilters.filters' );
        }
 
        /**
diff --git a/resources/Resources.php b/resources/Resources.php
index 2e4a15d..f25a95e 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -1806,6 +1806,26 @@
 
        /* MediaWiki Special pages */
 
+       'mediawiki.rcfilters.filters' => [
+               'scripts' => [
+                       'resources/src/mediawiki.rcfilters/mw.rcfilters.js',
+                       
'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js',
+                       
'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js',
+                       
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js',
+                       
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js',
+                       
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js',
+                       
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js',
+                       
'resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js',
+                       
'resources/src/mediawiki.rcfilters/mw.rcfilters.init.js',
+               ],
+               'styles' => [
+                       
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less',
+               ],
+               'dependencies' => [
+                       'oojs-ui',
+               ],
+               'position' => 'top',
+       ],
        'mediawiki.special' => [
                'position' => 'top',
                'styles' => 
'resources/src/mediawiki.special/mediawiki.special.css',
diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js 
b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js
new file mode 100644
index 0000000..e4dca08
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js
@@ -0,0 +1,75 @@
+( function ( mw ) {
+       /**
+        * Filter item model
+        *
+        * @mixins OO.EventEmitter
+        *
+        * @constructor
+        * @param {string} name Filter name
+        * @param {Object} config Configuration object
+        * @cfg {boolean} [selected] Filter is selected
+        */
+       mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( name, 
config ) {
+               config = config || {};
+
+               // Mixin constructor
+               OO.EventEmitter.call( this );
+
+               this.name = name;
+               this.label = config.label || this.name;
+               this.description = config.description;
+
+               this.selected = !!config.selected;
+       };
+
+       /* Initialization */
+
+       OO.initClass( mw.rcfilters.dm.FilterItem );
+       OO.mixinClass( mw.rcfilters.dm.FilterItem, OO.EventEmitter );
+
+       /* Events */
+
+       /**
+        * @event update
+        * @param {boolean} isSelected Filter is selected
+        * @param {string} color Color used to mark this filter
+        *
+        * The state of this filter has changed
+        */
+
+       /* Methods */
+
+       /**
+        * Get the name of this filter
+        */
+       mw.rcfilters.dm.FilterItem.prototype.getName = function () {
+               return this.name;
+       };
+       /**
+        * Get the label of this filter
+        */
+       mw.rcfilters.dm.FilterItem.prototype.getLabel = function () {
+               return this.label;
+       };
+       /**
+        * Get the description of this filter
+        */
+       mw.rcfilters.dm.FilterItem.prototype.getDescription = function () {
+               return this.description;
+       };
+
+       /**
+        * Toggle the selected state of the item
+        *
+        * @param {boolean} isSelected Filter is selected
+        * @fires update
+        */
+       mw.rcfilters.dm.FilterItem.prototype.toggleSelected = function ( 
isSelected ) {
+               isSelected = isSelected === undefined ? !this.selected : 
isSelected;
+
+               if ( this.selected !== isSelected ) {
+                       this.selected = isSelected;
+                       this.emit( 'update', this.selected );
+               }
+       };
+} )( mediaWiki );
diff --git 
a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js 
b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
new file mode 100644
index 0000000..4f82abf
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
@@ -0,0 +1,84 @@
+( function ( mw ) {
+       /**
+        * View model for the filters selection and display
+        *
+        * @mixins OO.EventEmitter
+        * @mixins OO.EmitterList
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        */
+       mw.rcfilters.dm.FiltersViewModel = function 
MwRcfiltersDmFiltersViewModel( config ) {
+               config = config || {};
+
+               // Mixin constructor
+               OO.EventEmitter.call( this );
+               OO.EmitterList.call( this );
+
+               this.groups = {};
+
+               // Events
+               this.aggregate( { update: 'itemUpdate' } );
+       };
+
+       /* Initialization */
+       OO.initClass( mw.rcfilters.dm.FiltersViewModel );
+       OO.mixinClass( mw.rcfilters.dm.FiltersViewModel, OO.EventEmitter );
+       OO.mixinClass( mw.rcfilters.dm.FiltersViewModel, OO.EmitterList );
+
+       /* Events */
+
+       /**
+        * @event update
+        *
+        * Filter list has changed
+        */
+
+       /* Methods */
+
+       /**
+        * Set filters and preserve a group relationship based on
+        * the definition given by an object
+        *
+        * @param {Object} filters Filter group definition
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.setFilters = function ( 
filters ) {
+               var i, filterItem, group,
+                       items = [];
+
+               for ( group in filters ) {
+                       this.groups[ group ] = this.groups[ group ] || [];
+
+                       for ( i = 0; i < filters[ group ].length; i++ ) {
+                               filterItem = new mw.rcfilters.dm.FilterItem( 
filters[ group ][ i ].name, {
+                                       label: filters[ group ][ i ].label,
+                                       description: filters[ group ][ i 
].description
+                               } );
+
+                               this.groups[ group ].push( filterItem );
+                               items.push( filterItem );
+                       }
+               }
+
+               this.addItems( items );
+               this.emit( 'update' );
+       };
+
+       /**
+        * Get the names of all available filters
+        *
+        * @return {string[]} An array of filter names
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getFilterNames = function () 
{
+               return this.getItems().map( function ( item ) { return 
item.getName(); } );
+       };
+
+       /**
+        * Get the object that defines groups and their filter items
+        *
+        * @return {Object} Filter groups
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getFilterGroups = function 
() {
+               return this.groups;
+       };
+} )( mediaWiki );
diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js 
b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
new file mode 100644
index 0000000..f85ecf2
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
@@ -0,0 +1,17 @@
+( function ( mw, $ ) {
+       /**
+        * Controller for the filters in Recent Changes
+        *
+        * @param {mw.rcfilters.dm.FiltersViewModel} model View model
+        * @param {Object} [config] Configuration
+        */
+       mw.rcfilters.Controller = function MwRcfiltersController( model, config 
) {
+               config = config || {};
+
+               this.model = model;
+       };
+
+       /* Initialization */
+       OO.initClass( mw.rcfilters.Controller );
+
+} )( mediaWiki, jQuery );
diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js 
b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
new file mode 100644
index 0000000..c9661f6
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
@@ -0,0 +1,40 @@
+/*!
+ * JavaScript for Special:RecentChanges
+ */
+( function ( mw, $ ) {
+       var rcfilters
+
+       /**
+        * @class mw.rcfilters
+        * @singleton
+        */
+       rcfilters = {
+               /** */
+               init: function () {
+                       var model = new mw.rcfilters.dm.FiltersViewModel(),
+                               widget = new 
mw.rcfilters.ui.FilterWrapperWidget( model );
+
+                       model.setFilters( {
+                               authorship: [
+                                       {
+                                               name: 'editsbyself',
+                                               label: mw.msg( 
'mw-rcfilters-editsbyself-label' ),
+                                               description: mw.msg( 
'mw-rcfilters-editsbyself-description' )
+                                       },
+                                       {
+                                               name: 'editsbyother',
+                                               label: mw.msg( 
'mw-rcfilters-editsbyother-label' ),
+                                               description: mw.msg( 
'mw-rcfilters-editsbyother-description' )
+                                       }
+                               ]
+                       } );
+
+                       $( '.mw-specialpage-summary' ).after( widget.$element );
+               }
+       };
+
+       $( rcfilters.init );
+
+       module.exports = rcfilters;
+
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.js 
b/resources/src/mediawiki.rcfilters/mw.rcfilters.js
new file mode 100644
index 0000000..b4bc734
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.js
@@ -0,0 +1,3 @@
+( function ( mw ) {
+       mw.rcfilters = { dm: {}, ui: {} };
+} )( mediaWiki );
diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less 
b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
new file mode 100644
index 0000000..7f71c0c
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
@@ -0,0 +1,5 @@
+.rcshowhidemine {
+       // HACK: Hide this filter since it already appears in
+       // the new filter drop-down.
+       display: none;
+}
diff --git 
a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js 
b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js
new file mode 100644
index 0000000..572a442
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js
@@ -0,0 +1,43 @@
+( function ( mw, $ ) {
+       /**
+        * A group of filters
+        *
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {string} name Group name
+        * @param {Object} config Configuration object
+        */
+       mw.rcfilters.ui.FilterGroupWidget = function 
MwRcfiltersUiFilterGroupWidget( config ) {
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.FilterGroupWidget.parent.call( this, config );
+               // Mixin constructors
+               OO.ui.mixin.GroupWidget.call( this, config );
+
+               this.$title = $( '<div>' )
+                       .addClass( 'mw-rcfilters-filterGroupWidget-title' );
+
+               if ( config.title ) {
+                       this.$element
+                               .append(
+                                       this.$title
+                                               .text( mw.msg( config.title ) )
+                               );
+               }
+
+               this.$element
+                       .addClass( 'mw-rcfilters-filterGroupWidget' )
+                       .append(
+                               this.$group
+                                       .addClass( 
'mw-rcfilters-filterGroupWidget-group' )
+                       );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.Widget );
+       OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, 
OO.ui.mixin.GroupWidget );
+
+} )( mediaWiki, jQuery );
diff --git 
a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js 
b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js
new file mode 100644
index 0000000..1a364b1
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js
@@ -0,0 +1,62 @@
+( function ( mw, $ ) {
+       /**
+        * A group of filters
+        *
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {string} name Group name
+        * @param {Object} config Configuration object
+        */
+       mw.rcfilters.ui.FilterItemWidget = function 
MwRcfiltersUiFilterItemWidget( model, config ) {
+               var layout,
+                       $label = $( '<div>' )
+                               .addClass( 
'mw-rcfilters-filterItemWidget-label' );
+
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.FilterItemWidget.parent.call( this, config );
+
+               this.model = model;
+
+               this.checkboxWidget = new OO.ui.CheckboxInputWidget( {
+                       value: this.model.getName()
+               } );
+
+               $label.append(
+                       $( '<div>' )
+                               .addClass( 
'mw-rcfilters-filterItemWidget-label-title' )
+                               .text( this.model.getLabel() )
+               );
+               if ( config.description ) {
+                       $label.append(
+                               $( '<div>' )
+                                       .addClass( 
'mw-rcfilters-filterItemWidget-label-desc' )
+                                       .text( config.description )
+                       );
+               }
+
+               layout = new OO.ui.FieldLayout( this.checkboxWidget, {
+                       label: $label,
+                       align: 'inline'
+               } );
+
+               this.checkboxWidget.connect( this, { change: 'onCheckboxChange' 
} );
+
+               this.$element
+                       .addClass( 'mw-rcfilters-filterItemWidget' )
+                       .append(
+                               layout.$element
+                       );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.FilterItemWidget, OO.ui.Widget );
+
+       mw.rcfilters.ui.FilterItemWidget.prototype.onCheckboxChange = function 
( isSelected ) {
+               this.model.toggleSelected( isSelected );
+       };
+
+} )( mediaWiki, jQuery );
diff --git 
a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js 
b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js
new file mode 100644
index 0000000..f674a0a
--- /dev/null
+++ 
b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js
@@ -0,0 +1,80 @@
+( function ( mw ) {
+       /**
+        * List displaying all filter groups
+        *
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {mw.rcfilters.dm.FiltersViewModel} model View model
+        * @param {Object} config Configuration object
+        * @cfg {Object} [filters] A definition of the filter groups in this 
list
+        */
+       mw.rcfilters.ui.FilterWrapperWidget = function 
MwRcfiltersUiFilterWrapperWidget( model, config ) {
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.FilterWrapperWidget.parent.call( this, config );
+               // Mixin constructors
+               OO.ui.mixin.PendingElement.call( this, config );
+
+               this.model = model;
+
+               this.filterPopup = new mw.rcfilters.ui.FiltersListWidget( 
this.model );
+
+               this.capsule = new OO.ui.CapsuleMultiselectWidget( {
+                       allowArbitrary: true,
+                       popup: { $content: this.filterPopup.$element }
+               } );
+
+               // Events
+               this.model.connect( this, {
+                       update: 'onModelUpdate',
+                       itemUpdate: 'onModelItemUpdate'
+               } );
+
+               this.$element
+                       .addClass( 'mw-rcfilters-ui-filterWrapperWidget' )
+                       .append( this.capsule.$element );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.Widget );
+       OO.mixinClass( mw.rcfilters.ui.FilterWrapperWidget, 
OO.ui.mixin.PendingElement );
+
+       /**
+        * Respond to model update event and set up the available filters to 
choose
+        * from.
+        */
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelUpdate = function 
() {
+               var i,
+                       items = [],
+                       filters = this.model.getItems();
+
+               // Insert hidden options for the capsule to get its item data 
from
+               for ( i = 0; i < filters.length; i++ ) {
+                       items.push(
+                               new OO.ui.MenuOptionWidget( {
+                                       data: filters[ i ].getName(),
+                                       label: filters[ i ].getLabel()
+                               } )
+                       );
+               }
+
+               this.capsule.getMenu().addItems( items );
+       };
+
+       /**
+        * Respond to model item update
+        *
+        * @param {mw.rcfilters.dm.FilterItem} item Filter item that was updated
+        * @param {boolean} isSelected State of the filter
+        */
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelItemUpdate = 
function ( item, isSelected ) {
+               if ( isSelected ) {
+                       this.capsule.addItemsFromData( [ item.getName() ] );
+               } else {
+                       this.capsule.removeItemsFromData( [ item.getName() ] );
+               }
+       };
+} )( mediaWiki );
diff --git 
a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js 
b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js
new file mode 100644
index 0000000..229ff81
--- /dev/null
+++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js
@@ -0,0 +1,67 @@
+( function ( mw ) {
+       /**
+        * List displaying all filter groups
+        *
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        * @cfg {Object} [filters] A definition of the filter groups in this 
list
+        */
+       mw.rcfilters.ui.FiltersListWidget = function 
MwRcfiltersUiFiltersListWidget( model, config ) {
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.FiltersListWidget.parent.call( this, config );
+               // Mixin constructors
+               OO.ui.mixin.GroupWidget.call( this, config );
+
+               this.model = model;
+
+               // Events
+               this.model.connect( this, {
+                       update: 'onModelUpdate'
+               } );
+
+               this.$element
+                       .addClass( 'mw-rcfilters-ui-filtersListWidget' )
+                       .append( this.$group );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.Widget );
+       OO.mixinClass( mw.rcfilters.ui.FiltersListWidget, 
OO.ui.mixin.GroupWidget );
+
+       /* Methods */
+       mw.rcfilters.ui.FiltersListWidget.prototype.onModelUpdate = function () 
{
+               var i, group, groupWidget,
+                       itemWidgets = [],
+                       groupWidgets = [],
+                       groups = this.model.getFilterGroups();
+
+               for ( group in groups ) {
+                       groupWidget = new mw.rcfilters.ui.FilterGroupWidget( {
+                               title: mw.msg( 'rcfilters-filtergroup-' + group 
)
+                       } );
+                       groupWidgets.push( groupWidget );
+
+                       itemWidgets = [];
+                       for ( i = 0; i < groups[ group ].length; i++ ) {
+                               itemWidgets.push(
+                                       new mw.rcfilters.ui.FilterItemWidget(
+                                               groups[ group ][ i ],
+                                               {
+                                                       label: groups[ group ][ 
i ].getLabel(),
+                                                       description: groups[ 
group ][ i ].getDescription()
+                                               }
+                                       )
+                               );
+                       }
+
+                       groupWidget.addItems( itemWidgets );
+               }
+
+               this.addItems( groupWidgets );
+       };
+} )( mediaWiki );

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic545ff1462998b610d7edae59472ecce2e7d51ea
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
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