'mediawiki.rcfilters.filters.ui' => [
'scripts' => [
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CheckboxInputWidget.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.CapsuleItemWidget.js',
- 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js',
+ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js',
+ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js',
+ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuSectionOptionWidget.js',
+ '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.FilterFloatingMenuSelectWidget.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.FormWrapperWidget.js',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.variables.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.less',
- 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less',
- 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.CapsuleItemWidget.less',
- 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterGroupWidget.less',
- 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FiltersListWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagMultiselectWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuOptionWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuSectionOptionWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagItemWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuHeaderWidget.less',
+ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less',
- 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.HighlightColorPickerWidget.less',
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemHighlightButton.less',
* Find items whose labels match the given string
*
* @param {string} query Search string
+ * @param {boolean} [returnFlat] Return a flat array. If false, the result
+ * is an object whose keys are the group names and values are an array of
+ * filters per group. If set to true, returns an array of filters regardless
+ * of their groups.
* @return {Object} An object of items to show
* arranged by their group names
*/
- mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( query ) {
+ mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( query, returnFlat ) {
var i,
groupTitle,
result = {},
+ flatResult = [],
items = this.getItems();
// Normalize so we can search strings regardless of case
if ( items[ i ].getLabel().toLowerCase().indexOf( query ) === 0 ) {
result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
result[ items[ i ].getGroupName() ].push( items[ i ] );
+ flatResult.push( items[ i ] );
}
}
) {
result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
result[ items[ i ].getGroupName() ].push( items[ i ] );
+ flatResult.push( items[ i ] );
}
}
}
- return result;
+ return returnFlat ? flatResult : result;
};
/**
+++ /dev/null
-@import 'mw.rcfilters.mixins';
-
-.mw-rcfilters-ui-capsuleItemWidget {
- background-color: #fff;
- border-color: #979797;
- color: #222;
-
- // Background and color of the capsule widget need a bit
- // more specificity to override ooui internals
- &-muted.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
- // Muted state
- background-color: #eaecf0;
- border-color: #c8ccd1;
-
- .oo-ui-labelElement-label {
- color: #72777d;
- }
- .oo-ui-buttonWidget {
- opacity: @muted-opacity;
- }
- }
-
- &-conflicted.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
- background-color: #fee7e6; // Red90 AAA
- border-color: #b32424; // Red30 AAA
-
- .oo-ui-labelElement-label {
- color: #b32424;
- }
- .oo-ui-buttonWidget {
- opacity: @muted-opacity;
- }
- }
-
- &-selected.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
- background-color: #eaf3ff;
- border-color: #36c;
- }
-
- &-popup-content {
- padding: 0.5em;
- color: #54595d;
- }
-
- &.oo-ui-labelElement .oo-ui-labelElement-label {
- vertical-align: middle;
- cursor: pointer;
- }
-
- &-highlight {
- display: none;
- padding-right: 0.5em;
-
- &-highlighted {
- display: inline-block;
-
- }
-
- &[data-color='c1'] {
- .mw-rcfilters-mixin-circle( @highlight-c1, 10px, ~'0 0.5em 0 0' );
- }
- &[data-color='c2'] {
- .mw-rcfilters-mixin-circle( @highlight-c2, 10px, ~'0 0.5em 0 0' );
- }
- &[data-color='c3'] {
- .mw-rcfilters-mixin-circle( @highlight-c3, 10px, ~'0 0.5em 0 0' );
- }
- &[data-color='c4'] {
- .mw-rcfilters-mixin-circle( @highlight-c4, 10px, ~'0 0.5em 0 0' );
- }
- &[data-color='c5'] {
- .mw-rcfilters-mixin-circle( @highlight-c5, 10px, ~'0 0.5em 0 0' );
- }
- }
-}
+++ /dev/null
-.mw-rcfilters-ui-filterCapsuleMultiselectWidget {
- max-width: none;
-
- &.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
- background-color: #f8f9fa;
- border-radius: 2px 2px 0 0;
- padding: 0.3em 0.6em 0.6em 0.6em;
- margin-top: 1.6em;
- }
-
- .mw-rcfilters-ui-table {
- margin-top: 0.3em;
- }
-
- &-wrapper-content-title {
- font-weight: bold;
- color: #54595d;
- }
-
- &-emptyFilters {
- color: #72777d;
- }
-
- &-cell-filters {
- width: 100%;
- }
- &-cell-reset {
- text-align: right;
- padding-left: 0.5em;
- }
-}
--- /dev/null
+@import 'mediawiki.mixins';
+
+.mw-rcfilters-ui-filterFloatingMenuSelectWidget {
+ z-index: auto;
+ max-width: 650px;
+
+ &-body {
+ max-height: 70vh;
+ }
+
+ &-footer {
+ background-color: #f8f9fa;
+ text-align: right;
+ padding: 0.5em;
+ }
+}
+++ /dev/null
-@import 'mediawiki.mixins';
-
-.mw-rcfilters-ui-filterGroupWidget {
- padding-bottom: 0.5em;
-
- &-header {
- background: #eaecf0;
- padding: 0.5em 0.75em;
-
- &-title {
- // TODO: Unify colors with official design palette
- color: #555a5d;
- .box-sizing( border-box );
- display: inline-block;
- }
- }
-
- &-whatsThisButton {
- display: inline-block;
- margin-left: 1.5em;
-
- &.oo-ui-buttonElement {
- vertical-align: text-bottom;
-
- & > .oo-ui-buttonElement-button {
- font-weight: normal;
- // Override OOUI's definitions for button
- border-color: transparent;
- padding: 0;
-
- &:focus {
- box-shadow: none;
- outline: 0;
- }
- }
- }
-
- &-popup-content {
- padding: 1em;
-
- &-header {
- font-weight: bold;
- margin-bottom: 1em;
- }
-
- &-link {
- margin: 1em 0;
-
- }
-
- .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- margin-left: 0;
- }
- }
- }
-
- &-active {
- .mw-rcfilters-ui-filterGroupWidget-header-title {
- font-weight: bold;
- }
- }
-}
+++ /dev/null
-@import 'mediawiki.mixins';
-
-.mw-rcfilters-ui-filterItemWidget {
- padding: 0 0.5em;
- .box-sizing( border-box );
-
- &:not( :last-child ) {
- border-bottom: solid 1px #eaecf0; // Base 80 AAA
- }
-
- &:hover {
- background-color: #fbfbfb;
- }
-
- .mw-rcfilters-ui-table {
- padding-top: 0.5em;
- }
-
- &-muted {
- background-color: #f8f9fa; // Base90 AAA
- .mw-rcfilters-ui-filterItemWidget-label-title,
- .mw-rcfilters-ui-filterItemWidget-label-desc {
- color: #54595d; // Base20 AAA
- }
- }
-
- &-selected {
- background-color: #eaf3ff; // Accent90 AAA
- }
-
- &-label {
- &-title {
- font-weight: bold;
- font-size: 1.15em;
- color: #222;
- }
- &-desc {
- color: #464a4f;
- }
- }
-
- &-filterCheckbox {
- .oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
- // Override margin-top and -bottom rules from FieldLayout
- margin: 0 !important; /* stylelint-disable-line declaration-no-important */
- }
-
- .oo-ui-checkboxInputWidget {
- // Workaround for IE11 rendering issues. T162098
- display: block;
- }
- }
-
- &-highlightButton {
- width: 4em;
- padding-left: 1em;
- }
-}
--- /dev/null
+@import 'mediawiki.mixins';
+
+.mw-rcfilters-ui-filterMenuHeaderWidget {
+ &-title {
+ font-size: 1.2em;
+ padding: 0.75em 0.5em;
+ // TODO: Unify colors with official design palette
+ color: #54595d;
+ }
+
+ &-header {
+ border-bottom: 1px solid #c8ccd1;
+ background: #f8f9fa;
+ overflow: hidden;
+
+ &-highlight {
+ width: 1em;
+ vertical-align: middle;
+ // Using the same padding that the filter item
+ // uses, so the button is aligned with the highlight
+ // buttons for the filters
+ padding-right: 0.5em;
+ }
+
+ &-title {
+ width: 100%;
+ vertical-align: middle;
+ }
+ }
+}
--- /dev/null
+@import 'mediawiki.mixins';
+
+.mw-rcfilters-ui-filterMenuOptionWidget {
+ padding: 0 0.5em;
+ .box-sizing( border-box );
+
+ &:not( :last-child ) {
+ border-bottom: solid 1px #eaecf0; // Base 80 AAA
+ }
+
+ &:hover {
+ background-color: #fbfbfb;
+ }
+
+ .mw-rcfilters-ui-table {
+ padding-top: 0.5em;
+ }
+
+ &-muted {
+ background-color: #f8f9fa; // Base90 AAA
+ .mw-rcfilters-ui-filterMenuOptionWidget-label-title,
+ .mw-rcfilters-ui-filterMenuOptionWidget-label-desc {
+ color: #54595d; // Base20 AAA
+ }
+ }
+
+ &.oo-ui-optionWidget-selected {
+ background-color: #eaf3ff; // Accent90 AAA
+ }
+
+ &-label {
+ &-title {
+ font-weight: bold;
+ font-size: 1.15em;
+ color: #222;
+ }
+ &-desc {
+ color: #464a4f;
+ white-space: normal;
+ }
+ }
+
+ &-filterCheckbox {
+ .oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
+ // Override margin-top and -bottom rules from FieldLayout
+ margin: 0 !important; /* stylelint-disable-line declaration-no-important */
+ }
+
+ .oo-ui-checkboxInputWidget {
+ // Workaround for IE11 rendering issues. T162098
+ display: block;
+ }
+ }
+
+ &-highlightButton {
+ width: 4em;
+ padding-left: 1em;
+ }
+}
--- /dev/null
+@import 'mediawiki.mixins';
+
+.mw-rcfilters-ui-filterMenuSectionOptionWidget {
+ background: #eaecf0;
+ padding-bottom: 0.7em;
+
+ &-header {
+ padding: 0 0.75em;
+ // Use a high specificity to override OOUI
+ .oo-ui-optionWidget.oo-ui-labelElement &-title.oo-ui-labelElement-label {
+ // TODO: Unify colors with official design palette
+ color: #555a5d;
+ .box-sizing( border-box );
+ display: inline-block;
+ line-height: normal;
+ }
+ }
+
+ &-whatsThisButton {
+ margin-left: 1.5em;
+ &.oo-ui-buttonElement {
+ vertical-align: text-bottom;
+
+ & > .oo-ui-buttonElement-button {
+ font-weight: normal;
+ // Override OOUI's definitions for button
+ border-color: transparent;
+ padding: 0;
+
+ &:focus {
+ box-shadow: none;
+ outline: 0;
+ }
+ }
+ }
+
+ &-popup-content {
+ padding: 1em;
+
+ &-header {
+ font-weight: bold;
+ margin-bottom: 1em;
+ }
+
+ &-link {
+ margin: 1em 0;
+ }
+
+ .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ margin-left: 0;
+ }
+ }
+ }
+
+ &-active {
+ .mw-rcfilters-ui-filterMenuSectionOptionWidget-header-title {
+ font-weight: bold;
+ }
+ }
+}
--- /dev/null
+@import 'mw.rcfilters.mixins';
+
+.mw-rcfilters-ui-filterTagItemWidget {
+ background-color: #fff;
+ border-color: #979797;
+ color: #222;
+
+ // Background and color of the capsule widget need a bit
+ // more specificity to override ooui internals
+ &-muted.oo-ui-tagItemWidget.oo-ui-widget-enabled {
+ // Muted state
+ background-color: #eaecf0;
+ border-color: #c8ccd1;
+
+ .oo-ui-labelElement-label {
+ color: #72777d;
+ }
+ .oo-ui-buttonWidget {
+ opacity: @muted-opacity;
+ }
+ }
+
+ &-conflicted.oo-ui-tagItemWidget.oo-ui-widget-enabled {
+ background-color: #fee7e6; // Red90 AAA
+ border-color: #b32424; // Red30 AAA
+
+ .oo-ui-labelElement-label {
+ color: #b32424;
+ }
+ .oo-ui-buttonWidget {
+ opacity: @muted-opacity;
+ }
+ }
+
+ &-selected.oo-ui-tagItemWidget.oo-ui-widget-enabled {
+ background-color: #eaf3ff;
+ border-color: #36c;
+ }
+
+ &-popup-content {
+ padding: 0.5em;
+ color: #54595d;
+ }
+
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ vertical-align: middle;
+ cursor: pointer;
+ }
+
+ &-highlight {
+ display: none;
+ margin-right: 0.5em;
+ height: 100%;
+ width: 10px;
+
+ &-highlighted {
+ display: inline-block;
+ }
+
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ top: 50%;
+ }
+
+ &[data-color='c1']:before {
+ .mw-rcfilters-mixin-circle( @highlight-c1, 10px, ~'-5px 0.5em 0 0' );
+ }
+
+ &[data-color='c2']:before {
+ .mw-rcfilters-mixin-circle( @highlight-c2, 10px, ~'-5px 0.5em 0 0' );
+ }
+
+ &[data-color='c3']:before {
+ .mw-rcfilters-mixin-circle( @highlight-c3, 10px, ~'-5px 0.5em 0 0' );
+ }
+
+ &[data-color='c4']:before {
+ .mw-rcfilters-mixin-circle( @highlight-c4, 10px, ~'-5px 0.5em 0 0' );
+ }
+
+ &[data-color='c5']:before {
+ .mw-rcfilters-mixin-circle( @highlight-c5, 10px, ~'-5px 0.5em 0 0' );
+ }
+ }
+}
--- /dev/null
+.mw-rcfilters-ui-filterTagMultiselectWidget {
+ max-width: none;
+
+ &.oo-ui-widget-enabled .oo-ui-tagMultiselectWidget-handle {
+ border: 1px solid #a2a9b1;
+ border-bottom: 0;
+ background-color: #f8f9fa;
+ border-radius: 2px 2px 0 0;
+ padding: 0.3em 0.6em 0.6em 0.6em;
+ margin-top: 1.6em;
+ }
+
+ .mw-rcfilters-ui-table {
+ margin-top: 0.3em;
+ }
+
+ &-wrapper-content-title {
+ font-weight: bold;
+ color: #54595d;
+ }
+
+ &-emptyFilters {
+ color: #72777d;
+ }
+
+ &-cell-filters {
+ width: 100%;
+ }
+
+ &-cell-reset {
+ text-align: right;
+ padding-left: 0.5em;
+ }
+}
// Make sure this uses the interface direction, not the content direction
direction: ltr;
- &-popup {
- margin-top: 1px;
- max-width: 650px;
-
- .oo-ui-popupWidget-body {
- max-height: 70vh;
- }
-
- .oo-ui-popupWidget-footer {
- background-color: #f8f9fa;
- text-align: right;
- padding: 0.5em;
- }
- }
-
&-search {
max-width: none;
margin-top: -1px;
+++ /dev/null
-.mw-rcfilters-ui-filtersListWidget {
- &-title {
- font-size: 1.2em;
- padding: 0.75em 0.5em;
- // TODO: Unify colors with official design palette
- color: #54595d;
- }
-
- &-header {
- border-bottom: 1px solid #c8ccd1;
- background: #f8f9fa;
- overflow: hidden;
-
- &-highlight {
- width: 1em;
- vertical-align: middle;
- // Using the same padding that the filter item
- // uses, so the button is aligned with the highlight
- // buttons for the filters
- padding-right: 0.5em;
- }
-
- &-title {
- width: 100%;
- vertical-align: middle;
- }
- }
-
- &-noresults {
- padding: 0.5em;
- // TODO: Unify colors with official design palette
- color: #666;
- }
-}
+++ /dev/null
-( function ( mw, $ ) {
- /**
- * Extend OOUI's CapsuleItemWidget to also display a popup on hover.
- *
- * @class
- * @extends OO.ui.CapsuleItemWidget
- * @mixins OO.ui.mixin.PopupElement
- *
- * @constructor
- * @param {mw.rcfilters.Controller} controller
- * @param {mw.rcfilters.dm.FilterItem} model Item model
- * @param {Object} config Configuration object
- * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
- */
- mw.rcfilters.ui.CapsuleItemWidget = function MwRcfiltersUiCapsuleItemWidget( controller, model, config ) {
- // Configuration initialization
- config = config || {};
-
- this.controller = controller;
- this.model = model;
- this.popupLabel = new OO.ui.LabelWidget();
- this.$overlay = config.$overlay || this.$element;
- this.positioned = false;
- this.popupTimeoutShow = null;
- this.popupTimeoutHide = null;
-
- // Parent constructor
- mw.rcfilters.ui.CapsuleItemWidget.parent.call( this, $.extend( {
- data: this.model.getName(),
- label: this.model.getLabel()
- }, config ) );
-
- // Mixin constructors
- OO.ui.mixin.PopupElement.call( this, $.extend( {
- popup: {
- padded: false,
- align: 'center',
- position: 'above',
- $content: $( '<div>' )
- .addClass( 'mw-rcfilters-ui-capsuleItemWidget-popup-content' )
- .append( this.popupLabel.$element ),
- $floatableContainer: this.$element,
- classes: [ 'mw-rcfilters-ui-capsuleItemWidget-popup' ]
- }
- }, config ) );
-
- this.$highlight = $( '<div>' )
- .addClass( 'mw-rcfilters-ui-capsuleItemWidget-highlight' );
-
- // Events
- this.model.connect( this, { update: 'onModelUpdate' } );
-
- // Initialization
- this.$overlay.append( this.popup.$element );
- this.$element
- .prepend( this.$highlight )
- .attr( 'aria-haspopup', 'true' )
- .addClass( 'mw-rcfilters-ui-capsuleItemWidget' )
- .on( 'mousedown', this.onMouseDown.bind( this ) )
- .on( 'mouseenter', this.onMouseEnter.bind( this ) )
- .on( 'mouseleave', this.onMouseLeave.bind( this ) );
-
- this.setCurrentMuteState();
- this.setHighlightColor();
- };
-
- OO.inheritClass( mw.rcfilters.ui.CapsuleItemWidget, OO.ui.CapsuleItemWidget );
- OO.mixinClass( mw.rcfilters.ui.CapsuleItemWidget, OO.ui.mixin.PopupElement );
-
- /**
- * Respond to model update event
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.onModelUpdate = function () {
- this.setCurrentMuteState();
-
- this.setHighlightColor();
- };
-
- /**
- * Override mousedown event to prevent its propagation to the parent,
- * since the parent (the multiselect widget) focuses the popup when its
- * mousedown event is fired.
- *
- * @param {jQuery.Event} e Event
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseDown = function ( e ) {
- e.stopPropagation();
- };
-
- /**
- * Emit a click event when the capsule is clicked so we can aggregate this
- * in the parent (the capsule)
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.onClick = function () {
- this.emit( 'click' );
- };
-
- /**
- * Override the event listening to the item close button click
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.onCloseClick = function () {
- var element = this.getElementGroup();
-
- if ( element && $.isFunction( element.removeItems ) ) {
- element.removeItems( [ this ] );
- }
-
- // Respond to user removing the filter
- this.controller.clearFilter( this.model.getName() );
- };
-
- mw.rcfilters.ui.CapsuleItemWidget.prototype.setHighlightColor = function () {
- var selectedColor = this.model.isHighlightEnabled() ? this.model.getHighlightColor() : null;
-
- this.$highlight
- .attr( 'data-color', selectedColor )
- .toggleClass(
- 'mw-rcfilters-ui-capsuleItemWidget-highlight-highlighted',
- !!selectedColor
- );
- };
-
- /**
- * Set the current mute state for this item
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.setCurrentMuteState = function () {
- this.$element
- .toggleClass(
- 'mw-rcfilters-ui-capsuleItemWidget-muted',
- !this.model.isSelected() ||
- this.model.isIncluded() ||
- this.model.isFullyCovered()
- )
- .toggleClass(
- 'mw-rcfilters-ui-capsuleItemWidget-conflicted',
- this.model.isSelected() && this.model.isConflicted()
- );
- };
-
- /**
- * Respond to mouse enter event
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseEnter = function () {
- var labelText = this.model.getStateMessage();
-
- if ( labelText ) {
- this.popupLabel.setLabel( labelText );
-
- if ( !this.positioned ) {
- // Recalculate anchor position to be center of the capsule item
- this.popup.$anchor.css( 'margin-left', ( this.$element.width() / 2 ) );
- this.positioned = true;
- }
-
- // Set timeout for the popup to show
- this.popupTimeoutShow = setTimeout( function () {
- this.popup.toggle( true );
- }.bind( this ), 500 );
-
- // Cancel the hide timeout
- clearTimeout( this.popupTimeoutHide );
- this.popupTimeoutHide = null;
- }
- };
-
- /**
- * Respond to mouse leave event
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseLeave = function () {
- this.popupTimeoutHide = setTimeout( function () {
- this.popup.toggle( false );
- }.bind( this ), 250 );
-
- // Clear the show timeout
- clearTimeout( this.popupTimeoutShow );
- this.popupTimeoutShow = null;
- };
-
- /**
- * Set selected state on this widget
- *
- * @param {boolean} [isSelected] Widget is selected
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.toggleSelected = function ( isSelected ) {
- isSelected = isSelected !== undefined ? isSelected : !this.selected;
-
- if ( this.selected !== isSelected ) {
- this.selected = isSelected;
-
- this.$element.toggleClass( 'mw-rcfilters-ui-capsuleItemWidget-selected', this.selected );
- }
- };
-
- /**
- * Remove and destroy external elements of this widget
- */
- mw.rcfilters.ui.CapsuleItemWidget.prototype.destroy = function () {
- // Destroy the popup
- this.popup.$element.detach();
-
- // Disconnect events
- this.model.disconnect( this );
- this.closeButton.disconnect( this );
- };
-}( mediaWiki, jQuery ) );
mw.rcfilters.ui.CheckboxInputWidget.parent.call( this, config );
// Event
- this.$input.on( 'change', this.onUserChange.bind( this ) );
+ this.$input
+ // HACK: This widget just pretends to be a checkbox for visual purposes.
+ // In reality, all actions - setting to true or false, etc - are
+ // decided by the model, and executed by the controller. This means
+ // that we want to let the controller and model make the decision
+ // of whether to check/uncheck this checkboxInputWidget, and for that,
+ // we have to bypass the browser action that checks/unchecks it during
+ // click.
+ .on( 'click', false )
+ .on( 'change', this.onUserChange.bind( this ) );
};
/* Initialization */
/* Methods */
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.CheckboxInputWidget.prototype.onEdit = function () {
+ // Similarly to preventing defaults in 'click' event, we want
+ // to prevent this widget from deciding anything about its own
+ // state; it emits a change event and the model and controller
+ // make a decision about what its select state is.
+ // onEdit has a widget.$input.prop( 'checked' ) inside a setTimeout()
+ // so we really want to prevent that from messing with what
+ // the model decides the state of the widget is.
+ };
+
/**
* Respond to checkbox change by a user and emit 'userChange'.
*/
+++ /dev/null
-( function ( mw, $ ) {
- /**
- * Filter-specific CapsuleMultiselectWidget
- *
- * @class
- * @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
- * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( controller, model, filterInput, config ) {
- var title = new OO.ui.LabelWidget( {
- label: mw.msg( 'rcfilters-activefilters' ),
- classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-wrapper-content-title' ]
- } ),
- $contentWrapper = $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-wrapper' );
-
- this.$overlay = config.$overlay || this.$element;
-
- // Parent
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.call( this, $.extend( true, {
- popup: {
- $autoCloseIgnore: filterInput.$element.add( this.$overlay ),
- $floatableContainer: filterInput.$element
- }
- }, config ) );
-
- this.controller = controller;
- this.model = model;
- this.filterInput = filterInput;
- this.isSelecting = false;
- this.selected = null;
-
- this.resetButton = new OO.ui.ButtonWidget( {
- framed: false,
- classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-resetButton' ]
- } );
-
- this.emptyFilterMessage = new OO.ui.LabelWidget( {
- label: mw.msg( 'rcfilters-empty-filter' ),
- classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-emptyFilters' ]
- } );
- this.$content.append( this.emptyFilterMessage.$element );
-
- // Events
- this.resetButton.connect( this, { click: 'onResetButtonClick' } );
- this.resetButton.$element.on( 'mousedown', this.onResetButtonMouseDown.bind( this ) );
- this.model.connect( this, {
- itemUpdate: 'onModelItemUpdate',
- highlightChange: 'onModelHighlightChange'
- } );
- this.aggregate( { click: 'capsuleItemClick' } );
-
- // Add the filterInput as trigger
- this.filterInput.$input
- .on( 'focus', this.focus.bind( this ) );
-
- // Build the content
- $contentWrapper.append(
- title.$element,
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-table' )
- .append(
- // The filter list 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-row' )
- .append(
- this.$content
- .addClass( 'mw-rcfilters-ui-cell' )
- .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell-filters' ),
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-cell' )
- .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell-reset' )
- .append( this.resetButton.$element )
- )
- )
- );
-
- // Initialize
- this.$handle.append( $contentWrapper );
-
- this.$element
- .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget' );
-
- this.reevaluateResetRestoreState();
- };
-
- /* Initialization */
-
- OO.inheritClass( mw.rcfilters.ui.FilterCapsuleMultiselectWidget, OO.ui.CapsuleMultiselectWidget );
-
- /* Events */
-
- /**
- * @event remove
- * @param {string[]} filters Array of names of removed filters
- *
- * Filters were removed
- */
-
- /* Methods */
-
- /**
- * Respond to model itemUpdate event
- *
- * @param {mw.rcfilters.dm.FilterItem} item Filter item model
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
- if (
- item.isSelected() ||
- (
- this.model.isHighlightEnabled() &&
- item.isHighlightSupported() &&
- item.getHighlightColor()
- )
- ) {
- this.addItemByName( item.getName() );
- } else {
- this.removeItemByName( item.getName() );
- }
-
- // Re-evaluate reset state
- this.reevaluateResetRestoreState();
- };
-
- /**
- * Respond to highlightChange event
- *
- * @param {boolean} isHighlightEnabled Highlight is enabled
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelHighlightChange = function ( isHighlightEnabled ) {
- var highlightedItems = this.model.getHighlightedItems();
-
- if ( isHighlightEnabled ) {
- // Add capsule widgets
- highlightedItems.forEach( function ( filterItem ) {
- this.addItemByName( filterItem.getName() );
- }.bind( this ) );
- } else {
- // Remove capsule widgets if they're not selected
- highlightedItems.forEach( function ( filterItem ) {
- if ( !filterItem.isSelected() ) {
- this.removeItemByName( filterItem.getName() );
- }
- }.bind( this ) );
- }
- };
-
- /**
- * 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();
- }
- };
-
- /**
- * Respond to mouse down event on the reset button to prevent the popup from opening
- *
- * @param {jQuery.Event} e Event
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onResetButtonMouseDown = function ( e ) {
- e.stopPropagation();
- };
-
- /**
- * 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.setTitle(
- currFiltersAreEmpty ? null : mw.msg( 'rcfilters-clear-all-filters' )
- );
-
- this.resetButton.toggle( !hideResetButton );
- this.emptyFilterMessage.toggle( currFiltersAreEmpty );
- };
-
- /**
- * Mark an item widget as selected
- *
- * @param {mw.rcfilters.ui.CapsuleItemWidget} item Capsule widget
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.select = function ( item ) {
- if ( this.selected !== item ) {
- // Unselect previous
- if ( this.selected ) {
- this.selected.toggleSelected( false );
- }
-
- // Select new one
- this.selected = item;
- if ( this.selected ) {
- item.toggleSelected( true );
- }
- }
- };
-
- /**
- * Reset selection and remove selected states from all items
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.resetSelection = function () {
- if ( this.selected !== null ) {
- this.selected = null;
- this.getItems().forEach( function ( capsuleWidget ) {
- capsuleWidget.toggleSelected( false );
- } );
- }
- };
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.createItemWidget = function ( data ) {
- var item = this.model.getItemByName( data );
-
- if ( !item ) {
- return;
- }
-
- return new mw.rcfilters.ui.CapsuleItemWidget(
- this.controller,
- item,
- { $overlay: this.$overlay }
- );
- };
-
- /**
- * Add items by their filter name
- *
- * @param {string} name Filter name
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.addItemByName = function ( name ) {
- var item = this.model.getItemByName( name );
-
- if ( !item ) {
- return;
- }
-
- // Check that the item isn't already added
- if ( !this.getItemFromData( name ) ) {
- this.addItems( [ this.createItemWidget( name ) ] );
- }
- };
-
- /**
- * Remove items by their filter name
- *
- * @param {string} name Filter name
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItemByName = function ( name ) {
- this.removeItemsFromData( [ name ] );
- };
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.focus = function () {
- // Override this method; we don't want to focus on the popup, and we
- // don't want to bind the size to the handle.
- if ( !this.isDisabled() ) {
- this.popup.toggle( true );
- this.filterInput.$input.get( 0 ).focus();
- }
- return this;
- };
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onFocusForPopup = function () {
- // HACK can be removed once I21b8cff4048 is merged in oojs-ui
- this.focus();
- };
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onKeyDown = function () {};
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onPopupFocusOut = function () {};
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.clearInput = function () {
- if ( this.filterInput ) {
- this.filterInput.setValue( '' );
- }
- this.menu.toggle( false );
- this.menu.selectItem();
- this.menu.highlightItem();
- };
-
- /**
- * @inheritdoc
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItems = function ( items ) {
- // Parent call
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.prototype.removeItems.call( this, items );
-
- // Destroy the item widget when it is removed
- // This is done because we re-add items by recreating them, rather than hiding them
- // and items include popups, that will just continue to be created and appended
- // unnecessarily.
- items.forEach( function ( widget ) {
- widget.destroy();
- } );
- };
-
- /**
- * Override 'editItem' since it tries to use $input which does
- * not exist when a popup is available.
- */
- mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.editItem = function () {};
-}( mediaWiki, jQuery ) );
--- /dev/null
+( function ( mw ) {
+ /**
+ * A floating menu widget for the filter list
+ *
+ * @extends OO.ui.FloatingMenuSelectWidget
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller Controller
+ * @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
+ */
+ mw.rcfilters.ui.FilterFloatingMenuSelectWidget = function MwRcfiltersUiFilterFloatingMenuSelectWidget( controller, model, config ) {
+ var header;
+
+ config = config || {};
+
+ this.controller = controller;
+ this.model = model;
+
+ this.inputValue = '';
+ this.$overlay = config.$overlay || this.$element;
+ this.$footer = config.$footer;
+ this.$body = $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-body' );
+
+ // Parent
+ mw.rcfilters.ui.FilterFloatingMenuSelectWidget.parent.call( this, $.extend( {
+ $autoCloseIgnore: this.$overlay,
+ width: 650
+ }, config ) );
+ this.setGroupElement(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-group' )
+ );
+ this.setClippableElement( this.$body );
+ this.setClippableContainer( this.$element );
+
+ header = new mw.rcfilters.ui.FilterMenuHeaderWidget(
+ this.controller,
+ this.model,
+ {
+ $overlay: this.$overlay
+ }
+ );
+
+ this.$element
+ .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget' )
+ .append(
+ this.$body
+ .append( header.$element, this.$group )
+ );
+
+ if ( this.$footer ) {
+ this.$element.append(
+ this.$footer
+ .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-footer' )
+ );
+ }
+ };
+
+ /* Initialize */
+
+ OO.inheritClass( mw.rcfilters.ui.FilterFloatingMenuSelectWidget, OO.ui.FloatingMenuSelectWidget );
+
+ /* Events */
+
+ /**
+ * @event itemVisibilityChange
+ *
+ * Item visibility has changed
+ */
+
+ /* Methods */
+
+ /**
+ * @fires itemVisibilityChange
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.updateItemVisibility = function () {
+ var i,
+ itemWasHighlighted = false,
+ inputVal = this.$input.val(),
+ items = this.getItems();
+
+ // Since the method hides/shows items, we don't want to
+ // call it unless the input actually changed
+ if ( this.inputValue !== inputVal ) {
+ // Parent method
+ mw.rcfilters.ui.FilterFloatingMenuSelectWidget.parent.prototype.updateItemVisibility.call( this );
+
+ if ( inputVal !== '' ) {
+ // Highlight the first item in the list
+ for ( i = 0; i < items.length; i++ ) {
+ if (
+ !( items[ i ] instanceof OO.ui.MenuSectionOptionWidget ) &&
+ items[ i ].isVisible()
+ ) {
+ itemWasHighlighted = true;
+ this.highlightItem( items[ i ] );
+ break;
+ }
+ }
+ }
+
+ if ( !itemWasHighlighted ) {
+ this.highlightItem( null );
+ }
+
+ // Cache value
+ this.inputValue = inputVal;
+
+ this.emit( 'itemVisibilityChange' );
+ }
+ };
+
+ /**
+ * Override the item matcher to use the model's match process
+ *
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.getItemMatcher = function ( s ) {
+ var results = this.model.findMatches( s, true );
+
+ return function ( item ) {
+ return results.indexOf( item.getModel() ) > -1;
+ };
+ };
+
+ /**
+ * Scroll to the top of the menu
+ */
+ mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.scrollToTop = function () {
+ this.$body.scrollTop( 0 );
+ };
+}( mediaWiki ) );
+++ /dev/null
-( function ( mw, $ ) {
- /**
- * A group of filters
- *
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.GroupWidget
- * @mixins OO.ui.mixin.LabelElement
- *
- * @constructor
- * @param {mw.rcfilters.Controller} controller Controller
- * @param {mw.rcfilters.dm.FilterGroup} model Filter group model
- * @param {Object} config Configuration object
- * @cfg {jQuery} [$overlay] Overlay
- */
- mw.rcfilters.ui.FilterGroupWidget = function MwRcfiltersUiFilterGroupWidget( controller, model, config ) {
- var whatsThisMessages,
- $header = $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterGroupWidget-header' ),
- $popupContent = $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content' );
-
- config = config || {};
-
- // Parent
- mw.rcfilters.ui.FilterGroupWidget.parent.call( this, config );
-
- this.controller = controller;
- this.model = model;
- this.filters = {};
- this.$overlay = config.$overlay || this.$element;
-
- // Mixin constructors
- OO.ui.mixin.GroupWidget.call( this, config );
- OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, {
- label: this.model.getTitle(),
- $label: $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterGroupWidget-header-title' )
- } ) );
-
- $header.append( this.$label );
-
- if ( this.model.hasWhatsThis() ) {
- whatsThisMessages = this.model.getWhatsThis();
-
- // Create popup
- if ( whatsThisMessages.header ) {
- $popupContent.append(
- ( new OO.ui.LabelWidget( {
- label: mw.msg( whatsThisMessages.header ),
- classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content-header' ]
- } ) ).$element
- );
- }
- if ( whatsThisMessages.body ) {
- $popupContent.append(
- ( new OO.ui.LabelWidget( {
- label: mw.msg( whatsThisMessages.body ),
- classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content-body' ]
- } ) ).$element
- );
- }
- if ( whatsThisMessages.linkText && whatsThisMessages.url ) {
- $popupContent.append(
- ( new OO.ui.ButtonWidget( {
- framed: false,
- flags: [ 'progressive' ],
- href: whatsThisMessages.url,
- label: mw.msg( whatsThisMessages.linkText ),
- classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content-link' ]
- } ) ).$element
- );
- }
-
- // Add button
- this.whatsThisButton = new OO.ui.PopupButtonWidget( {
- framed: false,
- label: mw.msg( 'rcfilters-filterlist-whatsthis' ),
- $overlay: this.$overlay,
- classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton' ],
- flags: [ 'progressive' ],
- popup: {
- padded: false,
- align: 'center',
- position: 'above',
- $content: $popupContent,
- classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup' ]
- }
- } );
-
- $header
- .append( this.whatsThisButton.$element );
- }
-
- // Populate
- this.populateFromModel();
-
- this.model.connect( this, { update: 'onModelUpdate' } );
-
- this.$element
- .addClass( 'mw-rcfilters-ui-filterGroupWidget' )
- .addClass( 'mw-rcfilters-ui-filterGroupWidget-name-' + this.model.getName() )
- .append(
- $header,
- this.$group
- .addClass( 'mw-rcfilters-ui-filterGroupWidget-group' )
- );
- };
-
- /* Initialization */
-
- OO.inheritClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.Widget );
- OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.GroupWidget );
- OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.LabelElement );
-
- /**
- * Respond to model update event
- */
- mw.rcfilters.ui.FilterGroupWidget.prototype.onModelUpdate = function () {
- this.$element.toggleClass(
- 'mw-rcfilters-ui-filterGroupWidget-active',
- this.model.isActive()
- );
- };
-
- /**
- * Get an item widget from its filter name
- *
- * @param {string} filterName Filter name
- * @return {mw.rcfilters.ui.FilterItemWidget} Item widget
- */
- mw.rcfilters.ui.FilterGroupWidget.prototype.getItemWidget = function ( filterName ) {
- return this.filters[ filterName ];
- };
-
- /**
- * Populate data from the model
- */
- mw.rcfilters.ui.FilterGroupWidget.prototype.populateFromModel = function () {
- var widget = this;
-
- this.clearItems();
- this.filters = {};
-
- this.addItems(
- this.model.getItems().map( function ( filterItem ) {
- var groupWidget = new mw.rcfilters.ui.FilterItemWidget(
- widget.controller,
- filterItem,
- {
- label: filterItem.getLabel(),
- description: filterItem.getDescription(),
- $overlay: widget.$overlay
- }
- );
-
- widget.filters[ filterItem.getName() ] = groupWidget;
-
- return groupWidget;
- } )
- );
- };
-
- /**
- * Get the group name
- *
- * @return {string} Group name
- */
- mw.rcfilters.ui.FilterGroupWidget.prototype.getName = function () {
- return this.model.getName();
- };
-}( mediaWiki, jQuery ) );
this.colorPickerWidget = new mw.rcfilters.ui.HighlightColorPickerWidget( controller, model );
// Parent
- mw.rcfilters.ui.FilterItemHighlightButton.parent.call( this, $.extend( {}, config, {
+ mw.rcfilters.ui.FilterItemHighlightButton.parent.call( this, $.extend( true, {}, config, {
icon: 'highlight',
indicator: 'down',
popup: {
// Event
this.model.connect( this, { update: 'onModelUpdate' } );
this.colorPickerWidget.connect( this, { chooseColor: 'onChooseColor' } );
+ // This lives inside a MenuOptionWidget, which intercepts mousedown
+ // to select the item. We want to prevent that when we click the highlight
+ // button
+ this.$element.on( 'mousedown', function ( e ) { e.stopPropagation(); } );
this.$element
.addClass( 'mw-rcfilters-ui-filterItemHighlightButton' );
+++ /dev/null
-( function ( mw, $ ) {
- /**
- * A widget representing a single toggle filter
- *
- * @extends OO.ui.Widget
- *
- * @constructor
- * @param {mw.rcfilters.Controller} controller RCFilters controller
- * @param {mw.rcfilters.dm.FilterItem} model Filter item model
- * @param {Object} config Configuration object
- */
- mw.rcfilters.ui.FilterItemWidget = function MwRcfiltersUiFilterItemWidget( controller, model, config ) {
- var layout,
- $label = $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterItemWidget-label' );
-
- config = config || {};
-
- // Parent
- mw.rcfilters.ui.FilterItemWidget.parent.call( this, config );
-
- this.controller = controller;
- this.model = model;
- this.selected = false;
-
- this.checkboxWidget = new mw.rcfilters.ui.CheckboxInputWidget( {
- value: this.model.getName(),
- selected: this.model.isSelected()
- } );
-
- $label.append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterItemWidget-label-title' )
- .text( this.model.getLabel() )
- );
- if ( this.model.getDescription() ) {
- $label.append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filterItemWidget-label-desc' )
- .text( this.model.getDescription() )
- );
- }
-
- this.highlightButton = new mw.rcfilters.ui.FilterItemHighlightButton(
- this.controller,
- this.model,
- {
- $overlay: config.$overlay || this.$element,
- title: mw.msg( 'rcfilters-highlightmenu-help' )
- }
- );
- this.highlightButton.toggle( this.model.isHighlightEnabled() );
-
- layout = new OO.ui.FieldLayout( this.checkboxWidget, {
- label: $label,
- align: 'inline'
- } );
-
- // Event
- this.checkboxWidget.connect( this, { userChange: 'onCheckboxChange' } );
- this.model.connect( this, { update: 'onModelUpdate' } );
- this.model.getGroupModel().connect( this, { update: 'onGroupModelUpdate' } );
-
- this.$element
- .addClass( 'mw-rcfilters-ui-filterItemWidget' )
- .append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-table' )
- .append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-row' )
- .append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterItemWidget-filterCheckbox' )
- .append( layout.$element ),
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterItemWidget-highlightButton' )
- .append( this.highlightButton.$element )
- )
- )
- );
- };
-
- /* Initialization */
-
- OO.inheritClass( mw.rcfilters.ui.FilterItemWidget, OO.ui.Widget );
-
- /* Methods */
-
- /**
- * Respond to checkbox change.
- * NOTE: This event is emitted both for deliberate user action and for
- * a change that the code requests ('setSelected')
- *
- * @param {boolean} isSelected The checkbox is selected
- */
- mw.rcfilters.ui.FilterItemWidget.prototype.onCheckboxChange = function ( isSelected ) {
- this.controller.toggleFilterSelect( this.model.getName(), isSelected );
- };
-
- /**
- * Respond to item model update event
- */
- mw.rcfilters.ui.FilterItemWidget.prototype.onModelUpdate = function () {
- this.checkboxWidget.setSelected( this.model.isSelected() );
-
- this.setCurrentMuteState();
- };
-
- /**
- * Respond to item group model update event
- */
- mw.rcfilters.ui.FilterItemWidget.prototype.onGroupModelUpdate = function () {
- this.setCurrentMuteState();
- };
-
- /**
- * Set selected state on this widget
- *
- * @param {boolean} [isSelected] Widget is selected
- */
- mw.rcfilters.ui.FilterItemWidget.prototype.toggleSelected = function ( isSelected ) {
- isSelected = isSelected !== undefined ? isSelected : !this.selected;
-
- if ( this.selected !== isSelected ) {
- this.selected = isSelected;
-
- this.$element.toggleClass( 'mw-rcfilters-ui-filterItemWidget-selected', this.selected );
- }
- };
-
- /**
- * Set the current mute state for this item
- */
- mw.rcfilters.ui.FilterItemWidget.prototype.setCurrentMuteState = function () {
- this.$element.toggleClass(
- 'mw-rcfilters-ui-filterItemWidget-muted',
- this.model.isConflicted() ||
- (
- // Item is also muted when any of the items in its group is active
- this.model.getGroupModel().isActive() &&
- // But it isn't selected
- !this.model.isSelected() &&
- // And also not included
- !this.model.isIncluded()
- )
- );
-
- this.highlightButton.toggle( this.model.isHighlightEnabled() );
- };
-
- /**
- * Get the name of this filter
- *
- * @return {string} Filter name
- */
- mw.rcfilters.ui.FilterItemWidget.prototype.getName = function () {
- return this.model.getName();
- };
-}( mediaWiki, jQuery ) );
--- /dev/null
+( function ( mw, $ ) {
+ /**
+ * Menu header for the RCFilters filters menu
+ *
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller Controller
+ * @param {mw.rcfilters.dm.FiltersViewModel} model View model
+ * @param {Object} config Configuration object
+ * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
+ */
+ mw.rcfilters.ui.FilterMenuHeaderWidget = function MwRcfiltersUiFilterMenuHeaderWidget( controller, model, config ) {
+ config = config || {};
+
+ this.controller = controller;
+ this.model = model;
+ this.$overlay = config.$overlay || this.$element;
+
+ // Parent
+ mw.rcfilters.ui.FilterMenuHeaderWidget.parent.call( this, config );
+ OO.ui.mixin.LabelElement.call( this, $.extend( {
+ label: mw.msg( 'rcfilters-filterlist-title' ),
+ $label: $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-title' )
+ }, config ) );
+
+ // Highlight button
+ this.highlightButton = new OO.ui.ToggleButtonWidget( {
+ icon: 'highlight',
+ label: mw.message( 'rcfilters-highlightbutton-title' ).text(),
+ classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-hightlightButton' ]
+ } );
+
+ // Events
+ this.highlightButton
+ .connect( this, { click: 'onHighlightButtonClick' } );
+ this.model.connect( this, { highlightChange: 'onModelHighlightChange' } );
+
+ // Initialize
+ this.$element
+ .addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-table' )
+ .addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-row' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header-title' )
+ .append( this.$label ),
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header-highlight' )
+ .append( this.highlightButton.$element )
+ )
+ )
+ );
+ };
+
+ /* Initialization */
+
+ OO.inheritClass( mw.rcfilters.ui.FilterMenuHeaderWidget, OO.ui.Widget );
+ OO.mixinClass( mw.rcfilters.ui.FilterMenuHeaderWidget, OO.ui.mixin.LabelElement );
+
+ /* Methods */
+
+ /**
+ * Respond to model highlight change event
+ *
+ * @param {boolean} highlightEnabled Highlight is enabled
+ */
+ mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelHighlightChange = function ( highlightEnabled ) {
+ this.highlightButton.setActive( highlightEnabled );
+ };
+
+ /**
+ * Respond to highlight button click
+ */
+ mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onHighlightButtonClick = function () {
+ this.controller.toggleHighlight();
+ };
+}( mediaWiki, jQuery ) );
--- /dev/null
+( function ( mw ) {
+ /**
+ * A widget representing a single toggle filter
+ *
+ * @extends OO.ui.MenuOptionWidget
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller RCFilters controller
+ * @param {mw.rcfilters.dm.FilterItem} model Filter item model
+ * @param {Object} config Configuration object
+ */
+ mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, model, config ) {
+ var layout,
+ $label = $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label' );
+
+ config = config || {};
+
+ this.controller = controller;
+ this.model = model;
+
+ // Parent
+ mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, $.extend( {
+ // Override the 'check' icon that OOUI defines
+ icon: '',
+ data: this.model.getName(),
+ label: this.model.getLabel()
+ }, config ) );
+
+ this.checkboxWidget = new mw.rcfilters.ui.CheckboxInputWidget( {
+ value: this.model.getName(),
+ selected: this.model.isSelected()
+ } );
+
+ $label.append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label-title' )
+ .append( this.$label )
+ );
+ if ( this.model.getDescription() ) {
+ $label.append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label-desc' )
+ .text( this.model.getDescription() )
+ );
+ }
+
+ this.highlightButton = new mw.rcfilters.ui.FilterItemHighlightButton(
+ this.controller,
+ this.model,
+ {
+ $overlay: config.$overlay || this.$element,
+ title: mw.msg( 'rcfilters-highlightmenu-help' )
+ }
+ );
+ this.highlightButton.toggle( this.model.isHighlightEnabled() );
+
+ layout = new OO.ui.FieldLayout( this.checkboxWidget, {
+ label: $label,
+ align: 'inline'
+ } );
+
+ // Event
+ this.model.connect( this, { update: 'onModelUpdate' } );
+ this.model.getGroupModel().connect( this, { update: 'onGroupModelUpdate' } );
+
+ this.$element
+ .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-table' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-row' )
+ .append(
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterMenuOptionWidget-filterCheckbox' )
+ .append( layout.$element ),
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterMenuOptionWidget-highlightButton' )
+ .append( this.highlightButton.$element )
+ )
+ )
+ );
+ };
+
+ /* Initialization */
+
+ OO.inheritClass( mw.rcfilters.ui.FilterMenuOptionWidget, OO.ui.MenuOptionWidget );
+
+ /* Static properties */
+
+ // We do our own scrolling to top
+ mw.rcfilters.ui.FilterMenuOptionWidget.static.scrollIntoViewOnSelect = false;
+
+ /* Methods */
+
+ /**
+ * Respond to item model update event
+ */
+ mw.rcfilters.ui.FilterMenuOptionWidget.prototype.onModelUpdate = function () {
+ this.checkboxWidget.setSelected( this.model.isSelected() );
+
+ this.setCurrentMuteState();
+ };
+
+ /**
+ * Respond to item group model update event
+ */
+ mw.rcfilters.ui.FilterMenuOptionWidget.prototype.onGroupModelUpdate = function () {
+ this.setCurrentMuteState();
+ };
+
+ /**
+ * Set the current mute state for this item
+ */
+ mw.rcfilters.ui.FilterMenuOptionWidget.prototype.setCurrentMuteState = function () {
+ this.$element.toggleClass(
+ 'mw-rcfilters-ui-filterMenuOptionWidget-muted',
+ this.model.isConflicted() ||
+ (
+ // Item is also muted when any of the items in its group is active
+ this.model.getGroupModel().isActive() &&
+ // But it isn't selected
+ !this.model.isSelected() &&
+ // And also not included
+ !this.model.isIncluded()
+ )
+ );
+
+ this.highlightButton.toggle( this.model.isHighlightEnabled() );
+ };
+
+ /**
+ * Get the name of this filter
+ *
+ * @return {string} Filter name
+ */
+ mw.rcfilters.ui.FilterMenuOptionWidget.prototype.getName = function () {
+ return this.model.getName();
+ };
+
+ mw.rcfilters.ui.FilterMenuOptionWidget.prototype.getModel = function () {
+ return this.model;
+ };
+
+}( mediaWiki ) );
--- /dev/null
+( function ( mw ) {
+ /**
+ * A widget representing a menu section for filter groups
+ *
+ * @extends OO.ui.MenuSectionOptionWidget
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller RCFilters controller
+ * @param {mw.rcfilters.dm.FilterGroup} model Filter group model
+ * @param {Object} config Configuration object
+ * @cfg {jQuery} [$overlay] Overlay
+ */
+ mw.rcfilters.ui.FilterMenuSectionOptionWidget = function MwRcfiltersUiFilterMenuSectionOptionWidget( controller, model, config ) {
+ var whatsThisMessages,
+ $header = $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-header' ),
+ $popupContent = $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content' );
+
+ config = config || {};
+
+ this.controller = controller;
+ this.model = model;
+ this.$overlay = config.$overlay || this.$element;
+
+ // Parent
+ mw.rcfilters.ui.FilterMenuSectionOptionWidget.parent.call( this, $.extend( {
+ label: this.model.getTitle(),
+ $label: $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-header-title' )
+ }, config ) );
+
+ $header.append( this.$label );
+
+ if ( this.model.hasWhatsThis() ) {
+ whatsThisMessages = this.model.getWhatsThis();
+
+ // Create popup
+ if ( whatsThisMessages.header ) {
+ $popupContent.append(
+ ( new OO.ui.LabelWidget( {
+ label: mw.msg( whatsThisMessages.header ),
+ classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content-header' ]
+ } ) ).$element
+ );
+ }
+ if ( whatsThisMessages.body ) {
+ $popupContent.append(
+ ( new OO.ui.LabelWidget( {
+ label: mw.msg( whatsThisMessages.body ),
+ classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content-body' ]
+ } ) ).$element
+ );
+ }
+ if ( whatsThisMessages.linkText && whatsThisMessages.url ) {
+ $popupContent.append(
+ ( new OO.ui.ButtonWidget( {
+ framed: false,
+ flags: [ 'progressive' ],
+ href: whatsThisMessages.url,
+ label: mw.msg( whatsThisMessages.linkText ),
+ classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content-link' ]
+ } ) ).$element
+ );
+ }
+
+ // Add button
+ this.whatsThisButton = new OO.ui.PopupButtonWidget( {
+ framed: false,
+ label: mw.msg( 'rcfilters-filterlist-whatsthis' ),
+ $overlay: this.$overlay,
+ classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton' ],
+ flags: [ 'progressive' ],
+ popup: {
+ $autoCloseIgnore: this.$element.add( this.$overlay ),
+ padded: false,
+ align: 'center',
+ position: 'above',
+ $content: $popupContent,
+ classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup' ]
+ }
+ } );
+
+ $header
+ .append( this.whatsThisButton.$element );
+ }
+
+ // Events
+ this.model.connect( this, { update: 'onModelUpdate' } );
+
+ // Initialize
+ this.$element
+ .addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget' )
+ .addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-name-' + this.model.getName() )
+ .append( $header );
+ };
+
+ /* Initialize */
+
+ OO.inheritClass( mw.rcfilters.ui.FilterMenuSectionOptionWidget, OO.ui.MenuSectionOptionWidget );
+
+ /* Methods */
+
+ /**
+ * Respond to model update event
+ */
+ mw.rcfilters.ui.FilterMenuSectionOptionWidget.prototype.onModelUpdate = function () {
+ this.$element.toggleClass(
+ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-active',
+ this.model.isActive()
+ );
+ };
+
+ /**
+ * Get the group name
+ *
+ * @return {string} Group name
+ */
+ mw.rcfilters.ui.FilterMenuSectionOptionWidget.prototype.getName = function () {
+ return this.model.getName();
+ };
+
+}( mediaWiki ) );
--- /dev/null
+( function ( mw, $ ) {
+ /**
+ * Extend OOUI's FilterTagItemWidget to also display a popup on hover.
+ *
+ * @class
+ * @extends OO.ui.FilterTagItemWidget
+ * @mixins OO.ui.mixin.PopupElement
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller
+ * @param {mw.rcfilters.dm.FilterItem} model Item model
+ * @param {Object} config Configuration object
+ * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
+ */
+ mw.rcfilters.ui.FilterTagItemWidget = function MwRcfiltersUiFilterTagItemWidget( controller, model, config ) {
+ // Configuration initialization
+ config = config || {};
+
+ this.controller = controller;
+ this.model = model;
+
+ mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, $.extend( {
+ data: this.model.getName(),
+ label: this.model.getLabel()
+ }, config ) );
+
+ this.$overlay = config.$overlay || this.$element;
+ this.popupLabel = new OO.ui.LabelWidget();
+
+ // Mixin constructors
+ OO.ui.mixin.PopupElement.call( this, $.extend( {
+ popup: {
+ padded: false,
+ align: 'center',
+ position: 'above',
+ $content: $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterTagItemWidget-popup-content' )
+ .append( this.popupLabel.$element ),
+ $floatableContainer: this.$element,
+ classes: [ 'mw-rcfilters-ui-filterTagItemWidget-popup' ]
+ }
+ }, config ) );
+
+ this.positioned = false;
+ this.popupTimeoutShow = null;
+ this.popupTimeoutHide = null;
+
+ this.$highlight = $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterTagItemWidget-highlight' );
+
+ // Events
+ this.model.connect( this, { update: 'onModelUpdate' } );
+
+ // Initialization
+ this.$overlay.append( this.popup.$element );
+ this.$element
+ .prepend( this.$highlight )
+ .attr( 'aria-haspopup', 'true' )
+ .on( 'mouseenter', this.onMouseEnter.bind( this ) )
+ .on( 'mouseleave', this.onMouseLeave.bind( this ) );
+
+ this.setCurrentMuteState();
+ this.setHighlightColor();
+ };
+
+ /* Initialization */
+
+ OO.inheritClass( mw.rcfilters.ui.FilterTagItemWidget, OO.ui.TagItemWidget );
+ OO.mixinClass( mw.rcfilters.ui.FilterTagItemWidget, OO.ui.mixin.PopupElement );
+
+ /* Methods */
+
+ /**
+ * Respond to model update event
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.onModelUpdate = function () {
+ this.setCurrentMuteState();
+
+ this.setHighlightColor();
+ };
+
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.setHighlightColor = function () {
+ var selectedColor = this.model.isHighlightEnabled() ? this.model.getHighlightColor() : null;
+
+ this.$highlight
+ .attr( 'data-color', selectedColor )
+ .toggleClass(
+ 'mw-rcfilters-ui-filterTagItemWidget-highlight-highlighted',
+ !!selectedColor
+ );
+ };
+
+ /**
+ * Set the current mute state for this item
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.setCurrentMuteState = function () {
+ this.$element
+ .toggleClass(
+ 'mw-rcfilters-ui-filterTagItemWidget-muted',
+ !this.model.isSelected() ||
+ this.model.isIncluded() ||
+ this.model.isFullyCovered()
+ )
+ .toggleClass(
+ 'mw-rcfilters-ui-filterTagItemWidget-conflicted',
+ this.model.isSelected() && this.model.isConflicted()
+ );
+ };
+
+ /**
+ * Respond to mouse enter event
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.onMouseEnter = function () {
+ var labelText = this.model.getStateMessage();
+
+ if ( labelText ) {
+ this.popupLabel.setLabel( labelText );
+
+ if ( !this.positioned ) {
+ // Recalculate anchor position to be center of the capsule item
+ this.popup.$anchor.css( 'margin-left', ( this.$element.width() / 2 ) );
+ this.positioned = true;
+ }
+
+ // Set timeout for the popup to show
+ this.popupTimeoutShow = setTimeout( function () {
+ this.popup.toggle( true );
+ }.bind( this ), 500 );
+
+ // Cancel the hide timeout
+ clearTimeout( this.popupTimeoutHide );
+ this.popupTimeoutHide = null;
+ }
+ };
+
+ /**
+ * Respond to mouse leave event
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.onMouseLeave = function () {
+ this.popupTimeoutHide = setTimeout( function () {
+ this.popup.toggle( false );
+ }.bind( this ), 250 );
+
+ // Clear the show timeout
+ clearTimeout( this.popupTimeoutShow );
+ this.popupTimeoutShow = null;
+ };
+
+ /**
+ * Set selected state on this widget
+ *
+ * @param {boolean} [isSelected] Widget is selected
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.toggleSelected = function ( isSelected ) {
+ isSelected = isSelected !== undefined ? isSelected : !this.selected;
+
+ if ( this.selected !== isSelected ) {
+ this.selected = isSelected;
+
+ this.$element.toggleClass( 'mw-rcfilters-ui-filterTagItemWidget-selected', this.selected );
+ }
+ };
+
+ /**
+ * Get item name
+ *
+ * @return {string} Filter name
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.getName = function () {
+ return this.model.getName();
+ };
+
+ /**
+ * Remove and destroy external elements of this widget
+ */
+ mw.rcfilters.ui.FilterTagItemWidget.prototype.destroy = function () {
+ // Destroy the popup
+ this.popup.$element.detach();
+
+ // Disconnect events
+ this.model.disconnect( this );
+ this.closeButton.disconnect( this );
+ };
+}( mediaWiki, jQuery ) );
--- /dev/null
+( function ( mw ) {
+ /**
+ * List displaying all filter groups
+ *
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.PendingElement
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller Controller
+ * @param {mw.rcfilters.dm.FiltersViewModel} model View model
+ * @param {Object} config Configuration object
+ * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget = function MwRcfiltersUiFilterTagMultiselectWidget( controller, model, config ) {
+ var title = new OO.ui.LabelWidget( {
+ label: mw.msg( 'rcfilters-activefilters' ),
+ classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-content-title' ]
+ } ),
+ $contentWrapper = $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper' );
+
+ config = config || {};
+
+ this.controller = controller;
+ this.model = model;
+ this.$overlay = config.$overlay || this.$element;
+
+ // Parent
+ mw.rcfilters.ui.FilterTagMultiselectWidget.parent.call( this, $.extend( true, {
+ label: mw.msg( 'rcfilters-filterlist-title' ),
+ placeholder: mw.msg( 'rcfilters-empty-filter' ),
+ inputPosition: 'outline',
+ allowArbitrary: false,
+ allowDisplayInvalidTags: false,
+ allowReordering: false,
+ $overlay: this.$overlay,
+ menu: {
+ 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
+ )
+ },
+ input: {
+ icon: 'search',
+ placeholder: mw.msg( 'rcfilters-search-placeholder' )
+ }
+ }, config ) );
+
+ this.resetButton = new OO.ui.ButtonWidget( {
+ framed: false,
+ classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-resetButton' ]
+ } );
+
+ this.emptyFilterMessage = new OO.ui.LabelWidget( {
+ label: mw.msg( 'rcfilters-empty-filter' ),
+ classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-emptyFilters' ]
+ } );
+ this.$content.append( this.emptyFilterMessage.$element );
+
+ // Events
+ this.resetButton.connect( this, { click: 'onResetButtonClick' } );
+ // Stop propagation for mousedown, so that the widget doesn't
+ // trigger the focus on the input and scrolls up when we click the reset button
+ this.resetButton.$element.on( 'mousedown', function ( e ) { e.stopPropagation(); } );
+ this.model.connect( this, {
+ initialize: 'onModelInitialize',
+ itemUpdate: 'onModelItemUpdate',
+ highlightChange: 'onModelHighlightChange'
+ } );
+ this.menu.connect( this, { toggle: 'onMenuToggle' } );
+
+ // Build the content
+ $contentWrapper.append(
+ title.$element,
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-table' )
+ .append(
+ // The filter list 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-row' )
+ .append(
+ this.$content
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-cell-filters' ),
+ $( '<div>' )
+ .addClass( 'mw-rcfilters-ui-cell' )
+ .addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-cell-reset' )
+ .append( this.resetButton.$element )
+ )
+ )
+ );
+
+ // Initialize
+ this.$handle.append( $contentWrapper );
+ this.emptyFilterMessage.toggle( this.isEmpty() );
+
+ this.$element
+ .addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget' );
+
+ this.populateFromModel();
+ this.reevaluateResetRestoreState();
+ };
+
+ /* Initialization */
+
+ OO.inheritClass( mw.rcfilters.ui.FilterTagMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
+
+ /* Methods */
+
+ /**
+ * Respond to menu toggle
+ *
+ * @param {boolean} isVisible Menu is visible
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onMenuToggle = function ( isVisible ) {
+ if ( isVisible ) {
+ mw.hook( 'RcFilters.popup.open' ).fire( this.getMenu().getSelectedItem() );
+
+ if ( !this.getMenu().getSelectedItem() ) {
+ // If there are no selected items, scroll menu to top
+ // This has to be in a setTimeout so the menu has time
+ // to be positioned and fixed
+ setTimeout( function () { this.getMenu().scrollToTop(); }.bind( this ), 0 );
+ }
+ } else {
+ // Clear selection
+ this.getMenu().selectItem( null );
+ }
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onInputFocus = function () {
+ // Parent
+ mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onInputFocus.call( this );
+
+ // Scroll to top
+ this.scrollToTop( this.$element );
+ };
+
+ /**
+ * @inheridoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onChangeTags = function () {
+ // Parent method
+ mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onChangeTags.call( this );
+
+ this.emptyFilterMessage.toggle( this.isEmpty() );
+ };
+
+ /**
+ * Respond to model initialize event
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelInitialize = function () {
+ this.populateFromModel();
+ };
+
+ /**
+ * Respond to model itemUpdate event
+ *
+ * @param {mw.rcfilters.dm.FilterItem} item Filter item model
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
+ if (
+ item.isSelected() ||
+ (
+ this.model.isHighlightEnabled() &&
+ item.isHighlightSupported() &&
+ item.getHighlightColor()
+ )
+ ) {
+ this.addTag( item.getName(), item.getLabel() );
+ } else {
+ this.removeTagByData( item.getName() );
+ }
+
+ // Re-evaluate reset state
+ this.reevaluateResetRestoreState();
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.isAllowedData = function ( data ) {
+ return (
+ this.menu.getItemFromData( data ) &&
+ !this.isDuplicateData( data )
+ );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onMenuChoose = function ( item ) {
+ this.controller.toggleFilterSelect( item.model.getName() );
+ };
+
+ /**
+ * Respond to highlightChange event
+ *
+ * @param {boolean} isHighlightEnabled Highlight is enabled
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelHighlightChange = function ( isHighlightEnabled ) {
+ var highlightedItems = this.model.getHighlightedItems();
+
+ if ( isHighlightEnabled ) {
+ // Add capsule widgets
+ highlightedItems.forEach( function ( filterItem ) {
+ this.addTag( filterItem.getName(), filterItem.getLabel() );
+ }.bind( this ) );
+ } else {
+ // Remove capsule widgets if they're not selected
+ highlightedItems.forEach( function ( filterItem ) {
+ if ( !filterItem.isSelected() ) {
+ this.removeTagByData( filterItem.getName() );
+ }
+ }.bind( this ) );
+ }
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onTagSelect = function ( tagItem ) {
+ var widget = this,
+ menuOption = this.menu.getItemFromData( tagItem.getData() );
+
+ // Reset input
+ this.input.setValue( '' );
+
+ // Parent method
+ mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onTagSelect.call( this, tagItem );
+
+ this.menu.selectItem( menuOption );
+
+ // Scroll to the item
+ // We're binding a 'once' to the itemVisibilityChange event
+ // so this happens when the menu is ready after the items
+ // are visible again, in case this is done right after the
+ // user filtered the results
+ this.getMenu().once(
+ 'itemVisibilityChange',
+ function () { widget.scrollToTop( menuOption.$element ); }
+ );
+ };
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onTagRemove = function ( tagItem ) {
+ // Parent method
+ mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onTagRemove.call( this, tagItem );
+
+ this.controller.clearFilter( tagItem.getName() );
+ };
+
+ /**
+ * Respond to click event on the reset button
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.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.FilterTagMultiselectWidget.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.setTitle(
+ currFiltersAreEmpty ? null : mw.msg( 'rcfilters-clear-all-filters' )
+ );
+
+ this.resetButton.toggle( !hideResetButton );
+ this.emptyFilterMessage.toggle( currFiltersAreEmpty );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createMenuWidget = function ( menuConfig ) {
+ return new mw.rcfilters.ui.FilterFloatingMenuSelectWidget(
+ this.controller,
+ this.model,
+ $.extend( {
+ filterFromInput: true
+ }, menuConfig )
+ );
+ };
+
+ /**
+ * Populate the menu from the model
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.populateFromModel = function () {
+ var widget = this,
+ items = [];
+
+ // Reset
+ this.getMenu().clearItems();
+
+ $.each( this.model.getFilterGroups(), function ( groupName, groupModel ) {
+ items.push(
+ // Group section
+ new mw.rcfilters.ui.FilterMenuSectionOptionWidget(
+ widget.controller,
+ groupModel,
+ {
+ $overlay: widget.$overlay
+ }
+ )
+ );
+
+ // Add items
+ widget.model.getGroupFilters( groupName ).forEach( function ( filterItem ) {
+ items.push(
+ new mw.rcfilters.ui.FilterMenuOptionWidget(
+ widget.controller,
+ filterItem,
+ {
+ $overlay: widget.$overlay
+ }
+ )
+ );
+ } );
+ } );
+
+ // Add all items to the menu
+ this.getMenu().addItems( items );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createTagItemWidget = function ( data ) {
+ var filterItem = this.model.getItemByName( data );
+
+ if ( filterItem ) {
+ return new mw.rcfilters.ui.FilterTagItemWidget(
+ this.controller,
+ filterItem,
+ {
+ $overlay: this.$overlay
+ }
+ );
+ }
+ };
+
+ /**
+ * Scroll the element to top within its container
+ *
+ * @private
+ * @param {jQuery} $element Element to position
+ * @param {number} [marginFromTop] When scrolling the entire widget to the top, leave this
+ * much space (in pixels) above the widget.
+ */
+ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.scrollToTop = function ( $element, marginFromTop ) {
+ var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ),
+ pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) ),
+ containerScrollTop = $( container ).is( 'body, html' ) ? 0 : $( container ).scrollTop();
+
+ // Scroll to item
+ $( container ).animate( {
+ scrollTop: containerScrollTop + pos.top - ( marginFromTop || 0 )
+ } );
+ };
+}( mediaWiki ) );
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
*/
mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( controller, model, config ) {
- var $footer = $( '<div>' );
config = config || {};
// Parent
this.model = model;
this.$overlay = config.$overlay || this.$element;
- this.filterPopup = new mw.rcfilters.ui.FiltersListWidget(
+ this.filterTagWidget = new mw.rcfilters.ui.FilterTagMultiselectWidget(
this.controller,
this.model,
- {
- label: mw.msg( 'rcfilters-filterlist-title' ),
- $overlay: this.$overlay
- }
+ { $overlay: this.$overlay }
);
- $footer.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
- );
-
- this.textInput = new OO.ui.TextInputWidget( {
- classes: [ 'mw-rcfilters-ui-filterWrapperWidget-search' ],
- icon: 'search',
- placeholder: mw.msg( 'rcfilters-search-placeholder' )
- } );
-
- this.capsule = new mw.rcfilters.ui.FilterCapsuleMultiselectWidget( controller, this.model, this.textInput, {
- $overlay: this.$overlay,
- popup: {
- $content: this.filterPopup.$element,
- $footer: $footer,
- classes: [ 'mw-rcfilters-ui-filterWrapperWidget-popup' ],
- width: 650,
- hideWhenOutOfView: false
- }
- } );
-
- // Events
- this.model.connect( this, {
- initialize: 'onModelInitialize',
- itemUpdate: 'onModelItemUpdate'
- } );
- this.textInput.connect( this, {
- change: 'onTextInputChange',
- enter: 'onTextInputEnter'
- } );
- this.capsule.connect( this, { capsuleItemClick: 'onCapsuleItemClick' } );
- this.capsule.popup.connect( this, {
- toggle: 'onCapsulePopupToggle',
- ready: 'onCapsulePopupReady'
- } );
-
// Initialize
this.$element
.addClass( 'mw-rcfilters-ui-filterWrapperWidget' )
- .append( this.capsule.$element, this.textInput.$element );
+ .append( this.filterTagWidget.$element );
};
/* Initialization */
OO.inheritClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.Widget );
OO.mixinClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.mixin.PendingElement );
-
- /**
- * Respond to capsule item click and make the popup scroll down to the requested item
- *
- * @param {mw.rcfilters.ui.CapsuleItemWidget} item Clicked item
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsuleItemClick = function ( item ) {
- var filterName = item.getData(),
- // Find the item in the popup
- filterWidget = this.filterPopup.getItemWidget( filterName );
-
- // Highlight item
- this.filterPopup.select( filterName );
- this.capsule.select( item );
-
- this.capsule.popup.toggle( true );
- this.scrollToTop( filterWidget.$element );
- };
-
- /**
- * Respond to capsule popup ready event, fired after the popup is visible, positioned and clipped
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsulePopupReady = function () {
- mw.hook( 'RcFilters.popup.open' ).fire( this.filterPopup.getSelectedFilter() );
-
- this.scrollToTop( this.capsule.$element, 10 );
- if ( !this.filterPopup.getSelectedFilter() ) {
- // No selection, scroll the popup list to top
- setTimeout( function () { this.capsule.popup.$body.scrollTop( 0 ); }.bind( this ), 0 );
- }
- };
-
- /**
- * Respond to popup toggle event. Reset selection in the list when the popup is closed.
- *
- * @param {boolean} isVisible Popup is visible
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsulePopupToggle = function ( isVisible ) {
- if ( !isVisible && !this.textInput.getValue() ) {
- // Only reset selection if we are not filtering
- this.filterPopup.resetSelection();
- this.capsule.resetSelection();
- }
- };
-
- /**
- * Respond to text input change
- *
- * @param {string} newValue Current value
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onTextInputChange = function ( newValue ) {
- // Filter the results
- this.filterPopup.filter( this.model.findMatches( newValue ) );
-
- if ( !newValue ) {
- // If the value is empty, we didn't actually
- // filter anything. the filter method will run
- // and show all, but then will select the
- // top item - but in this case, no selection
- // should be made.
- this.filterPopup.resetSelection();
- }
- this.capsule.popup.clip();
- };
-
- /**
- * Respond to text input enter event
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onTextInputEnter = function () {
- var filter = this.filterPopup.getSelectedFilter();
-
- // Toggle the filter
- if ( filter ) {
- this.controller.toggleFilterSelect( filter );
- }
- };
-
- /**
- * Respond to model update event and set up the available filters to choose
- * from.
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelInitialize = function () {
- var wrapper = this;
-
- // Add defaults to capsule. We have to do this
- // after we added to the capsule menu, since that's
- // how the capsule multiselect widget knows which
- // object to add
- this.model.getItems().forEach( function ( filterItem ) {
- if ( filterItem.isSelected() ) {
- wrapper.capsule.addItemByName( filterItem.getName() );
- }
- } );
- };
-
- /**
- * Respond to item update and reset the selection. This will make it so that
- * any actual interaction with the system resets the selection state of any item.
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelItemUpdate = function () {
- if ( !this.textInput.getValue() ) {
- this.filterPopup.resetSelection();
- }
- };
-
- /**
- * Scroll the element to top within its container
- *
- * @private
- * @param {jQuery} $element Element to position
- * @param {number} [marginFromTop] When scrolling the entire widget to the top, leave this
- * much space (in pixels) above the widget.
- */
- mw.rcfilters.ui.FilterWrapperWidget.prototype.scrollToTop = function ( $element, marginFromTop ) {
- var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ),
- pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) ),
- containerScrollTop = $( container ).is( 'body, html' ) ? 0 : $( container ).scrollTop();
-
- // Scroll to item
- $( container ).animate( {
- scrollTop: containerScrollTop + pos.top - ( marginFromTop || 0 )
- } );
- };
}( mediaWiki ) );
+++ /dev/null
-( function ( mw, $ ) {
- /**
- * List displaying all filter groups
- *
- * @extends OO.ui.Widget
- * @mixins OO.ui.mixin.GroupWidget
- * @mixins OO.ui.mixin.LabelElement
- *
- * @constructor
- * @param {mw.rcfilters.Controller} controller Controller
- * @param {mw.rcfilters.dm.FiltersViewModel} model View model
- * @param {Object} config Configuration object
- */
- mw.rcfilters.ui.FiltersListWidget = function MwRcfiltersUiFiltersListWidget( controller, model, config ) {
- config = config || {};
-
- // Parent
- mw.rcfilters.ui.FiltersListWidget.parent.call( this, config );
- // Mixin constructors
- OO.ui.mixin.GroupWidget.call( this, config );
- OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, {
- $label: $( '<div>' )
- .addClass( 'mw-rcfilters-ui-filtersListWidget-title' )
- } ) );
-
- this.controller = controller;
- this.model = model;
- this.$overlay = config.$overlay || this.$element;
- this.groups = {};
- this.selected = null;
-
- this.highlightButton = new OO.ui.ToggleButtonWidget( {
- icon: 'highlight',
- label: mw.message( 'rcfilters-highlightbutton-title' ).text(),
- classes: [ 'mw-rcfilters-ui-filtersListWidget-hightlightButton' ]
- } );
-
- this.noResultsLabel = new OO.ui.LabelWidget( {
- label: mw.msg( 'rcfilters-filterlist-noresults' ),
- classes: [ 'mw-rcfilters-ui-filtersListWidget-noresults' ]
- } );
-
- // Events
- this.highlightButton.connect( this, { click: 'onHighlightButtonClick' } );
- this.model.connect( this, {
- initialize: 'onModelInitialize',
- highlightChange: 'onModelHighlightChange'
- } );
-
- // Initialize
- this.showNoResultsMessage( false );
- this.$element
- .addClass( 'mw-rcfilters-ui-filtersListWidget' )
- .append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-table' )
- .addClass( 'mw-rcfilters-ui-filtersListWidget-header' )
- .append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-row' )
- .append(
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-cell' )
- .addClass( 'mw-rcfilters-ui-filtersListWidget-header-title' )
- .append( this.$label ),
- $( '<div>' )
- .addClass( 'mw-rcfilters-ui-cell' )
- .addClass( 'mw-rcfilters-ui-filtersListWidget-header-highlight' )
- .append( this.highlightButton.$element )
- )
- ),
- // this.$label,
- this.$group
- .addClass( 'mw-rcfilters-ui-filtersListWidget-group' ),
- this.noResultsLabel.$element
- );
- };
-
- /* Initialization */
-
- OO.inheritClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.Widget );
- OO.mixinClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.mixin.GroupWidget );
- OO.mixinClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.mixin.LabelElement );
-
- /* Methods */
-
- /**
- * Respond to initialize event from the model
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.onModelInitialize = function () {
- var widget = this;
-
- // Reset
- this.clearItems();
- this.groups = {};
-
- this.addItems(
- Object.keys( this.model.getFilterGroups() ).map( function ( groupName ) {
- var groupWidget = new mw.rcfilters.ui.FilterGroupWidget(
- widget.controller,
- widget.model.getGroup( groupName ),
- {
- $overlay: widget.$overlay
- }
- );
-
- widget.groups[ groupName ] = groupWidget;
- return groupWidget;
- } )
- );
- };
-
- /**
- * Respond to model highlight change event
- *
- * @param {boolean} highlightEnabled Highlight is enabled
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.onModelHighlightChange = function ( highlightEnabled ) {
- this.highlightButton.setActive( highlightEnabled );
- };
-
- /**
- * Respond to highlight button click
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.onHighlightButtonClick = function () {
- this.controller.toggleHighlight();
- };
-
- /**
- * Find the filter item widget that corresponds to the item name
- *
- * @param {string} itemName Filter name
- * @return {mw.rcfilters.ui.FilterItemWidget} Filter widget
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.getItemWidget = function ( itemName ) {
- var filterItem = this.model.getItemByName( itemName ),
- // Find the group
- groupWidget = this.groups[ filterItem.getGroupName() ];
-
- // Find the item inside the group
- return groupWidget.getItemWidget( itemName );
- };
-
- /**
- * Get the current selection
- *
- * @return {string|null} Selected filter. Null if none is selected.
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.getSelectedFilter = function () {
- return this.selected;
- };
-
- /**
- * Mark an item widget as selected
- *
- * @param {string} itemName Filter name
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.select = function ( itemName ) {
- var filterWidget;
-
- if ( this.selected !== itemName ) {
- // Unselect previous
- if ( this.selected ) {
- filterWidget = this.getItemWidget( this.selected );
- filterWidget.toggleSelected( false );
- }
-
- // Select new one
- this.selected = itemName;
- if ( this.selected ) {
- filterWidget = this.getItemWidget( this.selected );
- filterWidget.toggleSelected( true );
- }
- }
- };
-
- /**
- * Reset selection and remove selected states from all items
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.resetSelection = function () {
- if ( this.selected !== null ) {
- this.selected = null;
- this.getItems().forEach( function ( groupWidget ) {
- groupWidget.getItems().forEach( function ( filterItemWidget ) {
- filterItemWidget.toggleSelected( false );
- } );
- } );
- }
- };
-
- /**
- * Switch between showing the 'no results' message for filtering results or the result list.
- *
- * @param {boolean} showNoResults Show no results message
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.showNoResultsMessage = function ( showNoResults ) {
- this.noResultsLabel.toggle( !!showNoResults );
- this.$group.toggleClass( 'oo-ui-element-hidden', !!showNoResults );
- };
-
- /**
- * Show only the items matching with the models in the given list
- *
- * @param {Object} groupItems An object of items to show
- * arranged by their group names
- */
- mw.rcfilters.ui.FiltersListWidget.prototype.filter = function ( groupItems ) {
- var i, j, groupName, itemWidgets, topItem, isVisible,
- groupWidgets = this.getItems(),
- hasItemWithName = function ( itemArr, name ) {
- return !!itemArr.filter( function ( item ) {
- return item.getName() === name;
- } ).length;
- };
-
- this.resetSelection();
-
- if ( $.isEmptyObject( groupItems ) ) {
- // No results. Hide everything, show only 'no results'
- // message
- this.showNoResultsMessage( true );
- return;
- }
-
- this.showNoResultsMessage( false );
- for ( i = 0; i < groupWidgets.length; i++ ) {
- groupName = groupWidgets[ i ].getName();
-
- // If this group widget is in the filtered results,
- // show it - otherwise, hide it
- groupWidgets[ i ].toggle( !!groupItems[ groupName ] );
-
- if ( !groupItems[ groupName ] ) {
- // Continue to next group
- continue;
- }
-
- // We have items to show
- itemWidgets = groupWidgets[ i ].getItems();
- for ( j = 0; j < itemWidgets.length; j++ ) {
- isVisible = hasItemWithName( groupItems[ groupName ], itemWidgets[ j ].getName() );
- // Only show items that are in the filtered list
- itemWidgets[ j ].toggle( isVisible );
-
- if ( !topItem && isVisible ) {
- topItem = itemWidgets[ j ];
- }
- }
- }
-
- // Select the first item
- if ( topItem ) {
- this.select( topItem.getName() );
- }
- };
-}( mediaWiki, jQuery ) );