"recentchanges-legend-plusminus": "(<em>±123</em>)",
"recentchanges-submit": "Show",
"rcfilters-activefilters": "Active filters",
+ "rcfilters-restore-default-filters": "Restore default filters",
+ "rcfilters-clear-all-filters": "Clear all filters",
"rcfilters-search-placeholder": "Filter recent changes (browse or start typing)",
"rcfilters-invalid-filter": "Invalid filter",
+ "rcfilters-empty-filter": "No active filters. All contributions are shown.",
"rcfilters-filterlist-title": "Filters",
"rcfilters-filterlist-noresults": "No filters found",
"rcfilters-filtergroup-registration": "User registration",
"recentchanges-legend-plusminus": "{{optional}}\nA plus/minus sign with a number for the legend.",
"recentchanges-submit": "Label for submit button in [[Special:RecentChanges]]\n{{Identical|Show}}",
"rcfilters-activefilters": "Title for the filters selection showing the active filters.",
+ "rcfilters-restore-default-filters": "Label for the button that resets filters to defaults",
+ "rcfilters-clear-all-filters": "Title for the button that clears all filters",
"rcfilters-search-placeholder": "Placeholder for the filter search input.",
"rcfilters-invalid-filter": "A label for an invalid filter.",
+ "rcfilters-empty-filter": "Placeholder for the filter list when no filters were chosen.",
"rcfilters-filterlist-title": "Title for the filters list.\n{{Identical|Filter}}",
"rcfilters-filterlist-noresults": "Message showing no results found for searching a filter.",
"rcfilters-filtergroup-registration": "Title for the filter group for editor registration type.",
],
'messages' => [
'rcfilters-activefilters',
+ 'rcfilters-restore-default-filters',
+ 'rcfilters-clear-all-filters',
'rcfilters-search-placeholder',
'rcfilters-invalid-filter',
+ 'rcfilters-empty-filter',
'rcfilters-filterlist-title',
'rcfilters-filterlist-noresults',
'rcfilters-filtergroup-registration',
'dependencies' => [
'oojs-ui',
'mediawiki.Uri',
+ 'oojs-ui.styles.icons-moderation'
],
],
'mediawiki.special' => [
this.groups = {};
this.excludedByMap = {};
this.defaultParams = {};
+ this.defaultFiltersEmpty = null;
// Events
this.aggregate( { update: 'filterItemUpdate' } );
return result;
};
+ /**
+ * Check whether the current filter state is set to all false.
+ *
+ * @return {boolean} Current filters are all empty
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.areCurrentFiltersEmpty = function () {
+ var currFilters = this.getSelectedState();
+
+ return Object.keys( currFilters ).every( function ( filterName ) {
+ return !currFilters[ filterName ];
+ } );
+ };
+
+ /**
+ * Check whether the default values of the filters are all false.
+ *
+ * @return {boolean} Default filters are all false
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.areDefaultFiltersEmpty = function () {
+ var defaultFilters;
+
+ if ( this.defaultFiltersEmpty !== null ) {
+ // We only need to do this test once,
+ // because defaults are set once per session
+ defaultFilters = this.getFiltersFromParameters();
+ this.defaultFiltersEmpty = Object.keys( defaultFilters ).every( function ( filterName ) {
+ return !defaultFilters[ filterName ];
+ } );
+ }
+
+ return this.defaultFiltersEmpty;
+ };
+
/**
* This is the opposite of the #getParametersFromFilters method; this goes over
* the given parameters and translates into a selected/unselected value in the filters.
} )[ 0 ];
};
+ /**
+ * Set all filters to false or empty/all
+ * This is equivalent to display all.
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.emptyAllFilters = function () {
+ var filters = {};
+
+ this.getItems().forEach( function ( filterItem ) {
+ filters[ filterItem.getName() ] = false;
+ } );
+
+ // Update filters
+ this.updateFilters( filters );
+ };
+
/**
* Toggle selected state of items by their names
*
);
};
+ /**
+ * Reset to default filters
+ */
+ mw.rcfilters.Controller.prototype.resetToDefaults = function () {
+ this.model.setFiltersToDefaults();
+ };
+
+ /**
+ * Empty all selected filters
+ */
+ mw.rcfilters.Controller.prototype.emptyFilters = function () {
+ this.model.emptyAllFilters();
+ };
+
/**
* Update the state of a filter
*
opacity: 0.5;
}
+ &-emptyFilters {
+ color: #72777d;
+ }
+
+ &-table {
+ display: table;
+ width: 100%;
+ }
+
+ &-row {
+ display: table-row;
+ }
+
+ &-cell {
+ display: table-cell;
+
+ &:last-child {
+ text-align: right;
+ }
+ }
+
.oo-ui-capsuleItemWidget {
color: #222;
background-color: #fff;
&-popup {
// We have to override OOUI's definition, which is set
// on the inline style of the popup
- margin-top: 2em !important;
+ margin-top: 2.4em !important;
max-width: 650px;
}
* @extends OO.ui.CapsuleMultiselectWidget
*
* @constructor
+ * @param {mw.rcfilters.Controller} controller RCFilters controller
+ * @param {mw.rcfilters.dm.FiltersViewModel} model RCFilters view model
* @param {OO.ui.InputWidget} filterInput A filter input that focuses the capsule widget
* @param {Object} config Configuration object
*/
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( filterInput, config ) {
+ mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( controller, model, filterInput, config ) {
// Parent
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.call( this, $.extend( {
$autoCloseIgnore: filterInput.$element
}, config ) );
+ this.controller = controller;
+ this.model = model;
this.filterInput = filterInput;
this.$content.prepend(
.text( mw.msg( 'rcfilters-activefilters' ) )
);
+ this.resetButton = new OO.ui.ButtonWidget( {
+ icon: 'trash',
+ framed: false,
+ title: mw.msg( 'rcfilters-clear-all-filters' ),
+ classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-resetButton' ]
+ } );
+
+ this.emptyFilterMessage = new OO.ui.LabelWidget( {
+ label: mw.msg( 'rcfilters-empty-filter' ),
+ classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-emptyFilters' ]
+ } );
+
// Events
+ this.resetButton.connect( this, { click: 'onResetButtonClick' } );
+ this.model.connect( this, { itemUpdate: 'onModelItemUpdate' } );
// Add the filterInput as trigger
this.filterInput.$input
.on( 'focus', this.focus.bind( this ) );
+ // Initialize
+ this.$content.append( this.emptyFilterMessage.$element );
+ this.$handle
+ .append(
+ // The content and button should appear side by side regardless of how
+ // wide the button is; the button also changes its width depending
+ // on language and its state, so the safest way to present both side
+ // by side is with a table layout
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-table' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-row' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-content' )
+ .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell' )
+ .append( this.$content ),
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell' )
+ .append( this.resetButton.$element )
+ )
+ )
+ );
+
this.$element
.addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget' );
+
+ this.reevaluateResetRestoreState();
};
/* Initialization */
/* Methods */
+ mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelItemUpdate = function () {
+ // Re-evaluate reset state
+ this.reevaluateResetRestoreState();
+ };
+
+ /**
+ * Respond to click event on the reset button
+ */
+ mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onResetButtonClick = function () {
+ if ( this.model.areCurrentFiltersEmpty() ) {
+ // Reset to default filters
+ this.controller.resetToDefaults();
+ } else {
+ // Reset to have no filters
+ this.controller.emptyFilters();
+ }
+ };
+
+ /**
+ * Reevaluate the restore state for the widget between setting to defaults and clearing all filters
+ */
+ mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.reevaluateResetRestoreState = function () {
+ var defaultsAreEmpty = this.model.areDefaultFiltersEmpty(),
+ currFiltersAreEmpty = this.model.areCurrentFiltersEmpty(),
+ hideResetButton = currFiltersAreEmpty && defaultsAreEmpty;
+
+ this.resetButton.setIcon(
+ currFiltersAreEmpty ? 'history' : 'trash'
+ );
+
+ this.resetButton.setLabel(
+ currFiltersAreEmpty ? mw.msg( 'rcfilters-restore-default-filters' ) : ''
+ );
+
+ this.resetButton.toggle( !hideResetButton );
+ this.emptyFilterMessage.toggle( currFiltersAreEmpty );
+ };
+
/**
* @inheritdoc
*/
placeholder: mw.msg( 'rcfilters-search-placeholder' )
} );
- this.capsule = new mw.rcfilters.ui.FilterCapsuleMultiselectWidget( this.textInput, {
+ this.capsule = new mw.rcfilters.ui.FilterCapsuleMultiselectWidget( controller, this.model, this.textInput, {
popup: {
$content: this.filterPopup.$element,
classes: [ 'mw-rcfilters-ui-filterWrapperWidget-popup' ]