"recentchanges-legend-plusminus": "(<em>±123</em>)",
"recentchanges-submit": "Show",
"rcfilters-activefilters": "Active filters",
+ "rcfilters-advancedfilters": "Advanced filters",
"rcfilters-quickfilters": "Saved filter settings",
"rcfilters-quickfilters-placeholder-title": "No links saved yet",
"rcfilters-quickfilters-placeholder-description": "To save your filter settings and reuse them later, click the bookmark icon in the Active Filter area, below.",
"rcfilters-tag-prefix-namespace": ":$1",
"rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
"rcfilters-tag-prefix-tags": "#$1",
- "rcfilters-view-tags": "Tags",
+ "rcfilters-view-tags": "Tagged edits",
"rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
"rclistfromreset": "Reset date selection",
"rclistfrom": "Show new changes starting from $2, $3",
"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-advancedfilters": "Title for the buttons allowing the user to switch to the various advanced filters views.",
"rcfilters-quickfilters": "Label for the button that opens the saved filter settings menu in [[Special:RecentChanges]]",
"rcfilters-quickfilters-placeholder-title": "Title for the text shown in the quick filters menu on [[Special:RecentChanges]] if the user has not saved any quick filters.",
"rcfilters-quickfilters-placeholder-description": "Description for the text shown in the quick filters menu on [[Special:RecentChanges]] if the user has not saved any quick filters.",
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js',
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuHeaderWidget.js',
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MenuSelectWidget.js',
+ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ViewSwitchWidget.js',
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js',
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js',
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.SavedLinksListWidget.js',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.TagItemWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuHeaderWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.MenuSelectWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ViewSwitchWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.HighlightColorPickerWidget.less',
],
'messages' => [
'rcfilters-activefilters',
+ 'rcfilters-advancedfilters',
'rcfilters-quickfilters',
'rcfilters-quickfilters-placeholder-title',
'rcfilters-quickfilters-placeholder-description',
this.parameterMap = {};
this.views = {};
- this.currentView = null;
+ this.currentView = 'default';
// Events
this.aggregate( { update: 'filterItemUpdate' } );
* @return {string} View trigger, if exists
*/
mw.rcfilters.dm.FiltersViewModel.prototype.getViewTrigger = function ( view ) {
- return this.views[ view ] && this.views[ view ].trigger;
+ return ( this.views[ view ] && this.views[ view ].trigger ) || '';
};
/**
* Get the value of a specific parameter
padding-right: 0.5em;
}
+ &-back {
+ width: 1em;
+ vertical-align: middle;
+ padding-left: 0.5em;
+ }
+
&-title {
width: 100%;
vertical-align: middle;
margin-top: 1.6em;
}
- .mw-rcfilters-ui-table {
- margin-top: 0.3em;
+ &-wrapper {
+ .mw-rcfilters-ui-table {
+ margin-top: 0.3em;
+ }
+
+ &-content {
+ &-title {
+ font-weight: bold;
+ color: #54595d;
+ }
+
+ &-savedQueryTitle {
+ color: #72777d;
+ margin-left: 1em;
+ }
+ }
}
- &-wrapper-content {
- &-title {
- font-weight: bold;
- color: #54595d;
+ &-views {
+ &-input {
+ width: 100%;
}
- &-savedQueryTitle {
- color: #72777d;
- margin-left: 1em;
+ &-select {
+ width: 1em;
+
+ &-widget.oo-ui-widget {
+ display: block;
+ text-align: right;
+
+ // Override OOUI rules
+ &.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:first-child a.oo-ui-buttonElement-button,
+ .oo-ui-buttonOptionWidget a.oo-ui-buttonElement-button {
+ border-radius: 0;
+ border-left: 0;
+ }
+
+ &.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget:last-child a.oo-ui-buttonElement-button {
+ border-radius: 0;
+ }
+
+ }
}
}
}
&-footer {
- background-color: #f8f9fa;
- text-align: right;
padding: 0.5em;
+ background-color: #f8f9fa;
+ border-top: 1px solid #c8ccd1;
+
+ & + & {
+ border-top: 0;
+ }
+
+ &-feedback {
+ text-align: right;
+ }
}
}
--- /dev/null
+.mw-rcfilters-ui-viewSwitchWidget {
+ label.oo-ui-labelWidget {
+ color: #54595d;
+ font-weight: bold;
+ }
+
+ &-buttons {
+ margin-top: 0.5em;
+
+ .oo-ui-buttonWidget:not( :first-child ) {
+ margin-left: 0.5em;
+ }
+ }
+}
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-title' )
}, config ) );
+ // "Back" to default view button
+ this.backButton = new OO.ui.ButtonWidget( {
+ icon: 'previous',
+ framed: false,
+ title: mw.msg( 'rcfilters-filterlist-title' ),
+ classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-backButton' ]
+ } );
+ this.backButton.toggle( this.model.getCurrentView() !== 'default' );
+
// Highlight button
this.highlightButton = new OO.ui.ToggleButtonWidget( {
icon: 'highlight',
this.invertNamespacesButton.toggle( this.model.getCurrentView() === 'namespaces' );
// Events
+ this.backButton.connect( this, { click: 'onBackButtonClick' } );
this.highlightButton
.connect( this, { click: 'onHighlightButtonClick' } );
this.invertNamespacesButton
$( '<div>' )
.addClass( 'mw-rcfilters-ui-row' )
.append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header-back' )
+ .append( this.backButton.$element ),
$( '<div>' )
.addClass( 'mw-rcfilters-ui-cell' )
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header-title' )
* Respond to model update event
*/
mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelUpdate = function () {
+ var currentView = this.model.getCurrentView();
+
this.setLabel( this.model.getCurrentViewLabel() );
- this.invertNamespacesButton.toggle( this.model.getCurrentView() === 'namespaces' );
+ this.invertNamespacesButton.toggle( currentView === 'namespaces' );
+ this.backButton.toggle( currentView !== 'default' );
};
/**
this.invertNamespacesButton.setActive( isInverted );
};
+ mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onBackButtonClick = function () {
+ this.controller.switchView( 'default' );
+ };
+
/**
* Respond to highlight button click
*/
hideWhenOutOfView: false,
hideOnChoose: false,
width: 650,
- $footer: $( '<div>' )
- .append(
- new OO.ui.ButtonWidget( {
- framed: false,
- icon: 'feedback',
- flags: [ 'progressive' ],
- label: mw.msg( 'rcfilters-filterlist-feedbacklink' ),
- href: 'https://www.mediawiki.org/wiki/Help_talk:New_filters_for_edit_review'
- } ).$element
- )
+ footers: [
+ {
+ name: 'viewSelect',
+ // View select menu, appears on default view only
+ $element: $( '<div>' )
+ .append( new mw.rcfilters.ui.ViewSwitchWidget( this.controller, this.model ).$element ),
+ views: [ 'default' ]
+ },
+ {
+ name: 'feedback',
+ // Feedback footer, appears on all views
+ $element: $( '<div>' )
+ .append(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'feedback',
+ flags: [ 'progressive' ],
+ label: mw.msg( 'rcfilters-filterlist-feedbacklink' ),
+ href: 'https://www.mediawiki.org/wiki/Help_talk:New_filters_for_edit_review'
+ } ).$element
+ )
+ }
+ ]
},
input: {
icon: 'search',
);
}
+ if ( mw.config.get( 'wgStructuredChangeFiltersEnableExperimentalViews' ) ) {
+ // Add a selector at the right of the input
+ this.viewsSelectWidget = new OO.ui.ButtonSelectWidget( {
+ classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-views-select-widget' ],
+ items: [
+ new OO.ui.ButtonOptionWidget( {
+ data: 'namespaces',
+ icon: 'article',
+ title: mw.msg( 'namespaces' )
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'tags',
+ icon: 'tag',
+ title: mw.msg( 'rcfilters-view-tags' )
+ } )
+ ]
+ } );
+
+ // Rearrange the UI so the select widget is at the right of the input
+ this.$element.append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-table' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-row' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-views-input' )
+ .append( this.input.$element ),
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-views-select' )
+ .append( this.viewsSelectWidget.$element )
+ )
+ )
+ );
+
+ // Event
+ this.viewsSelectWidget.connect( this, { choose: 'onViewsSelectWidgetChoose' } );
+ }
+
rcFiltersRow.append(
$( '<div>' )
.addClass( 'mw-rcfilters-ui-cell' )
/* Methods */
+ /**
+ * Respond to view select widget choose event
+ *
+ * @param {OO.ui.ButtonOptionWidget} buttonOptionWidget Chosen widget
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onViewsSelectWidgetChoose = function ( buttonOptionWidget ) {
+ this.controller.switchView( buttonOptionWidget.getData() );
+ this.viewsSelectWidget.selectItem( null );
+ this.focus();
+ };
+
/**
* Respond to input change event
*
{ $overlay: this.$overlay }
);
- this.viewToggle = new OO.ui.ButtonSelectWidget( {
- classes: [ 'mw-rcfilters-ui-filterWrapperWidget-viewToggleButtons' ],
- items: [
- new OO.ui.ButtonOptionWidget( {
- data: 'namespaces',
- label: mw.msg( 'namespaces' ),
- icon: 'article',
- classes: [ 'mw-rcfilters-ui-filterWrapperWidget-viewToggleButtons-namespaces' ]
- } ),
- new OO.ui.ButtonOptionWidget( {
- data: 'tags',
- label: mw.msg( 'rcfilters-view-tags' ),
- icon: 'tag',
- classes: [ 'mw-rcfilters-ui-filterWrapperWidget-viewToggleButtons-tags' ]
- } )
- ]
- } );
-
- // Events
- this.model.connect( this, { update: 'onModelUpdate' } );
- this.viewToggle.connect( this, { select: 'onViewToggleSelect' } );
-
// Initialize
this.$element
.addClass( 'mw-rcfilters-ui-filterWrapperWidget' );
}
this.$element.append(
- this.filterTagWidget.$element,
- this.viewToggle.$element
+ this.filterTagWidget.$element
);
- this.viewToggle.toggle( !!mw.config.get( 'wgStructuredChangeFiltersEnableExperimentalViews' ) );
};
/* Initialization */
OO.inheritClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.Widget );
OO.mixinClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.mixin.PendingElement );
-
- /* Methods */
-
- /**
- * Respond to model update event
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelUpdate = function () {
- // Synchronize the state of the toggle buttons with the current view
- this.viewToggle.selectItemByData( this.model.getCurrentView() );
- };
-
- /**
- * Respond to namespace toggle button click
- *
- * @param {OO.ui.ButtonWidget} buttonWidget The button that was clicked
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onViewToggleSelect = function ( buttonWidget ) {
- if ( buttonWidget ) {
- this.controller.switchView( buttonWidget.getData() );
- this.filterTagWidget.focus();
- }
- };
-
}( mediaWiki ) );
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
* @param {Object} [config] Configuration object
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
- * @cfg {jQuery} [$footer] An optional footer for the menu
+ * @cfg {Object[]} [footers] An array of objects defining the footers for
+ * this menu, with a definition whether they appear per specific views.
+ * The expected structure is:
+ * [
+ * {
+ * name: {string} A unique name for the footer object
+ * $element: {jQuery} A jQuery object for the content of the footer
+ * views: {string[]} Optional. An array stating which views this footer is
+ * active on. Use null or omit to display this on all views.
+ * }
+ * ]
*/
mw.rcfilters.ui.MenuSelectWidget = function MwRcfiltersUiMenuSelectWidget( controller, model, config ) {
var header;
this.inputValue = '';
this.$overlay = config.$overlay || this.$element;
- this.$footer = config.$footer;
this.$body = $( '<div>' )
.addClass( 'mw-rcfilters-ui-menuSelectWidget-body' );
+ this.footers = [];
// Parent
mw.rcfilters.ui.MenuSelectWidget.parent.call( this, $.extend( {
.append( this.$group, this.noResults.$element )
);
- if ( this.$footer ) {
- this.$element.append(
- this.$footer
+ // Append all footers; we will control their visibility
+ // based on view
+ config.footers = config.footers || [];
+ config.footers.forEach( function ( footerData ) {
+ var adjustedData = {
+ // Wrap the element with our own footer wrapper
+ $element: $( '<div>' )
.addClass( 'mw-rcfilters-ui-menuSelectWidget-footer' )
- );
- }
+ .addClass( 'mw-rcfilters-ui-menuSelectWidget-footer-' + footerData.name )
+ .append( footerData.$element ),
+ views: footerData.views
+ };
+
+ this.footers.push( adjustedData );
+ this.$element.append( adjustedData.$element );
+ }.bind( this ) );
+
+ // Switch to the correct view
this.switchView( this.model.getCurrentView() );
};
if ( this.views[ viewName ] && this.currentView !== viewName ) {
this.clearItems();
this.addItems( this.views[ viewName ] );
+ this.updateFooterVisibility( viewName );
this.$element
.data( 'view', viewName )
.addClass( 'mw-rcfilters-ui-menuSelectWidget-view-' + viewName );
this.currentView = viewName;
+ this.clip();
}
};
+ /**
+ * Go over the available footers and decide which should be visible
+ * for this view
+ *
+ * @param {string} [currentView] Current view
+ */
+ mw.rcfilters.ui.MenuSelectWidget.prototype.updateFooterVisibility = function ( currentView ) {
+ currentView = currentView || this.model.getCurrentView();
+
+ this.footers.forEach( function ( data ) {
+ data.$element.toggle(
+ // This footer should only be shown if it is configured
+ // for all views or for this specific view
+ !data.views || data.views.length === 0 || data.views.indexOf( currentView ) > -1
+ );
+ } );
+ };
+
/**
* @fires itemVisibilityChange
* @inheritdoc
--- /dev/null
+( function ( mw ) {
+ /**
+ * A widget for the footer for the default view, allowing to switch views
+ *
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller Controller
+ * @param {mw.rcfilters.dm.FiltersViewModel} model View model
+ * @param {Object} [config] Configuration object
+ */
+ mw.rcfilters.ui.ViewSwitchWidget = function MwRcfiltersUiViewSwitchWidget( controller, model, config ) {
+ config = config || {};
+
+ // Parent
+ mw.rcfilters.ui.ViewSwitchWidget.parent.call( this, config );
+
+ this.controller = controller;
+ this.model = model;
+
+ this.buttons = new OO.ui.ButtonGroupWidget( {
+ items: [
+ new OO.ui.ButtonWidget( {
+ data: 'namespaces',
+ icon: 'article',
+ label: mw.msg( 'namespaces' )
+ } ),
+ new OO.ui.ButtonWidget( {
+ data: 'tags',
+ icon: 'tag',
+ label: mw.msg( 'rcfilters-view-tags' )
+ } )
+ ]
+ } );
+
+ // Events
+ this.model.connect( this, { update: 'onModelUpdate' } );
+ this.buttons.aggregate( { click: 'buttonClick' } );
+ this.buttons.connect( this, { buttonClick: 'onButtonClick' } );
+
+ this.$element
+ .addClass( 'mw-rcfilters-ui-viewSwitchWidget' )
+ .append(
+ new OO.ui.LabelWidget( {
+ label: mw.msg( 'rcfilters-advancedfilters' )
+ } ).$element,
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-viewSwitchWidget-buttons' )
+ .append( this.buttons.$element )
+ );
+ };
+
+ /* Initialize */
+
+ OO.inheritClass( mw.rcfilters.ui.ViewSwitchWidget, OO.ui.Widget );
+
+ /**
+ * Respond to model update event
+ */
+ mw.rcfilters.ui.ViewSwitchWidget.prototype.onModelUpdate = function () {
+ var currentView = this.model.getCurrentView();
+
+ this.buttons.getItems().forEach( function ( buttonWidget ) {
+ buttonWidget.setActive( buttonWidget.getData() === currentView );
+ } );
+ };
+
+ /**
+ * Respond to button switch click
+ *
+ * @param {OO.ui.ButtonWidget} buttonWidget Clicked button
+ */
+ mw.rcfilters.ui.ViewSwitchWidget.prototype.onButtonClick = function ( buttonWidget ) {
+ this.controller.switchView( buttonWidget.getData() );
+ };
+}( mediaWiki ) );