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