diff --git a/resources/Resources.php b/resources/Resources.php index 715cdb8c9e3..1721de807c8 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1742,6 +1742,7 @@ return [ 'mediawiki.rcfilters.filters.dm' => [ 'scripts' => [ 'resources/src/mediawiki.rcfilters/mw.rcfilters.js', + 'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js', 'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js', 'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js', 'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js', @@ -1757,11 +1758,13 @@ return [ 'scripts' => [ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CheckboxInputWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js', + 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ItemMenuOptionWidget.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.TagItemWidget.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.FloatingMenuSelectWidget.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', @@ -1776,11 +1779,12 @@ return [ '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.FilterTagMultiselectWidget.less', + 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ItemMenuOptionWidget.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.TagItemWidget.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.FloatingMenuSelectWidget.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', diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js index 221d2a54c63..4e2079dc408 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js @@ -2,37 +2,29 @@ /** * Filter item model * - * @mixins OO.EventEmitter + * @extends mw.rcfilters.dm.ItemModel * * @constructor * @param {string} param Filter param name * @param {mw.rcfilters.dm.FilterGroup} groupModel Filter group model * @param {Object} config Configuration object - * @cfg {string} [group] The group this item belongs to - * @cfg {string} [label] The label for the filter - * @cfg {string} [description] The description of the filter - * @cfg {boolean} [active=true] The filter is active and affecting the result * @cfg {string[]} [excludes=[]] A list of filter names this filter, if * selected, makes inactive. - * @cfg {boolean} [selected] The item is selected * @cfg {string[]} [subset] Defining the names of filters that are a subset of this filter * @cfg {Object} [conflicts] Defines the conflicts for this filter - * @cfg {string} [cssClass] The class identifying the results that match this filter */ mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( param, groupModel, config ) { config = config || {}; + this.groupModel = groupModel; + + // Parent + mw.rcfilters.dm.FilterItem.parent.call( this, param, $.extend( { + namePrefix: this.groupModel.getNamePrefix() + }, config ) ); // Mixin constructor OO.EventEmitter.call( this ); - this.param = param; - this.groupModel = groupModel; - this.name = this.groupModel.getNamePrefix() + param; - - this.label = config.label || this.name; - this.description = config.description; - this.selected = !!config.selected; - // Interaction definitions this.subset = config.subset || []; this.conflicts = config.conflicts || {}; @@ -42,25 +34,11 @@ this.included = false; this.conflicted = false; this.fullyCovered = false; - - // Highlight - this.cssClass = config.cssClass; - this.highlightColor = null; - this.highlightEnabled = false; }; /* Initialization */ - OO.initClass( mw.rcfilters.dm.FilterItem ); - OO.mixinClass( mw.rcfilters.dm.FilterItem, OO.EventEmitter ); - - /* Events */ - - /** - * @event update - * - * The state of this filter has changed - */ + OO.inheritClass( mw.rcfilters.dm.FilterItem, mw.rcfilters.dm.ItemModel ); /* Methods */ @@ -78,27 +56,10 @@ }; }; - /** - * Get the name of this filter - * - * @return {string} Filter name - */ - mw.rcfilters.dm.FilterItem.prototype.getName = function () { - return this.name; - }; - - /** - * Get the param name or value of this filter - * - * @return {string} Filter param name - */ - mw.rcfilters.dm.FilterItem.prototype.getParamName = function () { - return this.param; - }; - /** * Get the message for the display area for the currently active conflict * + * @private * @return {string} Conflict result message key */ mw.rcfilters.dm.FilterItem.prototype.getCurrentConflictResultMessage = function () { @@ -117,6 +78,7 @@ /** * Get the details of the active conflict on this filter * + * @private * @param {Object} conflicts Conflicts to examine * @param {string} [key='contextDescription'] Message key * @return {Object} Object with conflict message and conflict items @@ -153,9 +115,7 @@ }; /** - * Get the message representing the state of this model. - * - * @return {string} State message + * @inheritdoc */ mw.rcfilters.dm.FilterItem.prototype.getStateMessage = function () { var messageKey, details, superset, @@ -227,33 +187,6 @@ return this.groupModel.getName(); }; - /** - * Get the label of this filter - * - * @return {string} Filter label - */ - mw.rcfilters.dm.FilterItem.prototype.getLabel = function () { - return this.label; - }; - - /** - * Get the description of this filter - * - * @return {string} Filter description - */ - mw.rcfilters.dm.FilterItem.prototype.getDescription = function () { - return this.description; - }; - - /** - * Get the default value of this filter - * - * @return {boolean} Filter default - */ - mw.rcfilters.dm.FilterItem.prototype.getDefault = function () { - return this.default; - }; - /** * Get filter subset * This is a list of filter names that are defined to be included @@ -276,15 +209,6 @@ return this.superset; }; - /** - * Get the selected state of this filter - * - * @return {boolean} Filter is selected - */ - mw.rcfilters.dm.FilterItem.prototype.isSelected = function () { - return this.selected; - }; - /** * Check whether the filter is currently in a conflict state * @@ -431,21 +355,6 @@ } }; - /** - * Toggle the selected state of the item - * - * @param {boolean} [isSelected] Filter is selected - * @fires update - */ - mw.rcfilters.dm.FilterItem.prototype.toggleSelected = function ( isSelected ) { - isSelected = isSelected === undefined ? !this.selected : isSelected; - - if ( this.selected !== isSelected ) { - this.selected = isSelected; - this.emit( 'update' ); - } - }; - /** * Toggle the fully covered state of the item * @@ -460,90 +369,4 @@ this.emit( 'update' ); } }; - - /** - * Set the highlight color - * - * @param {string|null} highlightColor - */ - mw.rcfilters.dm.FilterItem.prototype.setHighlightColor = function ( highlightColor ) { - if ( this.highlightColor !== highlightColor ) { - this.highlightColor = highlightColor; - this.emit( 'update' ); - } - }; - - /** - * Clear the highlight color - */ - mw.rcfilters.dm.FilterItem.prototype.clearHighlightColor = function () { - this.setHighlightColor( null ); - }; - - /** - * Get the highlight color, or null if none is configured - * - * @return {string|null} - */ - mw.rcfilters.dm.FilterItem.prototype.getHighlightColor = function () { - return this.highlightColor; - }; - - /** - * Get the CSS class that matches changes that fit this filter - * or null if none is configured - * - * @return {string|null} - */ - mw.rcfilters.dm.FilterItem.prototype.getCssClass = function () { - return this.cssClass; - }; - - /** - * Toggle the highlight feature on and off for this filter. - * It only works if highlight is supported for this filter. - * - * @param {boolean} enable Highlight should be enabled - */ - mw.rcfilters.dm.FilterItem.prototype.toggleHighlight = function ( enable ) { - enable = enable === undefined ? !this.highlightEnabled : enable; - - if ( !this.isHighlightSupported() ) { - return; - } - - if ( enable === this.highlightEnabled ) { - return; - } - - this.highlightEnabled = enable; - this.emit( 'update' ); - }; - - /** - * Check if the highlight feature is currently enabled for this filter - * - * @return {boolean} - */ - mw.rcfilters.dm.FilterItem.prototype.isHighlightEnabled = function () { - return !!this.highlightEnabled; - }; - - /** - * Check if the highlight feature is supported for this filter - * - * @return {boolean} - */ - mw.rcfilters.dm.FilterItem.prototype.isHighlightSupported = function () { - return !!this.getCssClass(); - }; - - /** - * Check if the filter is currently highlighted - * - * @return {boolean} - */ - mw.rcfilters.dm.FilterItem.prototype.isHighlighted = function () { - return this.isHighlightEnabled() && !!this.getHighlightColor(); - }; }( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js new file mode 100644 index 00000000000..675fcc72ad2 --- /dev/null +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js @@ -0,0 +1,257 @@ +( function ( mw ) { + /** + * RCFilter base item model + * + * @mixins OO.EventEmitter + * + * @constructor + * @param {string} param Filter param name + * @param {Object} config Configuration object + * @cfg {string} [label] The label for the filter + * @cfg {string} [description] The description of the filter + * @cfg {boolean} [active=true] The filter is active and affecting the result + * @cfg {boolean} [selected] The item is selected + * @cfg {boolean} [inverted] The item is inverted, meaning the search is excluding + * this parameter. + * @cfg {string} [namePrefix='item_'] A prefix to add to the param name to act as a unique + * identifier + * @cfg {string} [cssClass] The class identifying the results that match this filter + */ + mw.rcfilters.dm.ItemModel = function MwRcfiltersDmItemModel( param, config ) { + config = config || {}; + + // Mixin constructor + OO.EventEmitter.call( this ); + + this.param = param; + this.namePrefix = config.namePrefix || 'item_'; + this.name = this.namePrefix + param; + + this.label = config.label || this.name; + this.description = config.description; + this.selected = !!config.selected; + + this.inverted = !!config.inverted; + + // Highlight + this.cssClass = config.cssClass; + this.highlightColor = null; + this.highlightEnabled = false; + }; + + /* Initialization */ + + OO.initClass( mw.rcfilters.dm.ItemModel ); + OO.mixinClass( mw.rcfilters.dm.ItemModel, OO.EventEmitter ); + + /* Events */ + + /** + * @event update + * + * The state of this filter has changed + */ + + /* Methods */ + + /** + * Return the representation of the state of this item. + * + * @return {Object} State of the object + */ + mw.rcfilters.dm.ItemModel.prototype.getState = function () { + return { + selected: this.isSelected(), + inverted: this.isInverted() + }; + }; + + /** + * Get the name of this filter + * + * @return {string} Filter name + */ + mw.rcfilters.dm.ItemModel.prototype.getName = function () { + return this.name; + }; + + /** + * Get the param name or value of this filter + * + * @return {string} Filter param name + */ + mw.rcfilters.dm.ItemModel.prototype.getParamName = function () { + return this.param; + }; + + /** + * Get the message representing the state of this model. + * + * @return {string} State message + */ + mw.rcfilters.dm.ItemModel.prototype.getStateMessage = function () { + // Display description + return this.getDescription(); + }; + + /** + * Get the label of this filter + * + * @return {string} Filter label + */ + mw.rcfilters.dm.ItemModel.prototype.getLabel = function () { + return this.label; + }; + + /** + * Get the description of this filter + * + * @return {string} Filter description + */ + mw.rcfilters.dm.ItemModel.prototype.getDescription = function () { + return this.description; + }; + + /** + * Get the default value of this filter + * + * @return {boolean} Filter default + */ + mw.rcfilters.dm.ItemModel.prototype.getDefault = function () { + return this.default; + }; + + /** + * Get the selected state of this filter + * + * @return {boolean} Filter is selected + */ + mw.rcfilters.dm.ItemModel.prototype.isSelected = function () { + return this.selected; + }; + + /** + * Toggle the selected state of the item + * + * @param {boolean} [isSelected] Filter is selected + * @fires update + */ + mw.rcfilters.dm.ItemModel.prototype.toggleSelected = function ( isSelected ) { + isSelected = isSelected === undefined ? !this.selected : isSelected; + + if ( this.selected !== isSelected ) { + this.selected = isSelected; + this.emit( 'update' ); + } + }; + + /** + * Get the inverted state of this item + * + * @return {boolean} Item is inverted + */ + mw.rcfilters.dm.ItemModel.prototype.isInverted = function () { + return this.inverted; + }; + + /** + * Toggle the inverted state of the item + * + * @param {boolean} [isInverted] Item is inverted + * @fires update + */ + mw.rcfilters.dm.ItemModel.prototype.toggleInverted = function ( isInverted ) { + isInverted = isInverted === undefined ? !this.inverted : isInverted; + + if ( this.inverted !== isInverted ) { + this.inverted = isInverted; + this.emit( 'update' ); + } + }; + + /** + * Set the highlight color + * + * @param {string|null} highlightColor + */ + mw.rcfilters.dm.ItemModel.prototype.setHighlightColor = function ( highlightColor ) { + if ( this.highlightColor !== highlightColor ) { + this.highlightColor = highlightColor; + this.emit( 'update' ); + } + }; + + /** + * Clear the highlight color + */ + mw.rcfilters.dm.ItemModel.prototype.clearHighlightColor = function () { + this.setHighlightColor( null ); + }; + + /** + * Get the highlight color, or null if none is configured + * + * @return {string|null} + */ + mw.rcfilters.dm.ItemModel.prototype.getHighlightColor = function () { + return this.highlightColor; + }; + + /** + * Get the CSS class that matches changes that fit this filter + * or null if none is configured + * + * @return {string|null} + */ + mw.rcfilters.dm.ItemModel.prototype.getCssClass = function () { + return this.cssClass; + }; + + /** + * Toggle the highlight feature on and off for this filter. + * It only works if highlight is supported for this filter. + * + * @param {boolean} enable Highlight should be enabled + */ + mw.rcfilters.dm.ItemModel.prototype.toggleHighlight = function ( enable ) { + enable = enable === undefined ? !this.highlightEnabled : enable; + + if ( !this.isHighlightSupported() ) { + return; + } + + if ( enable === this.highlightEnabled ) { + return; + } + + this.highlightEnabled = enable; + this.emit( 'update' ); + }; + + /** + * Check if the highlight feature is currently enabled for this filter + * + * @return {boolean} + */ + mw.rcfilters.dm.ItemModel.prototype.isHighlightEnabled = function () { + return !!this.highlightEnabled; + }; + + /** + * Check if the highlight feature is supported for this filter + * + * @return {boolean} + */ + mw.rcfilters.dm.ItemModel.prototype.isHighlightSupported = function () { + return !!this.getCssClass(); + }; + + /** + * Check if the filter is currently highlighted + * + * @return {boolean} + */ + mw.rcfilters.dm.ItemModel.prototype.isHighlighted = function () { + return this.isHighlightEnabled() && !!this.getHighlightColor(); + }; +}( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuOptionWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuOptionWidget.less index 9d78f854c9a..78ea014e6c7 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuOptionWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuOptionWidget.less @@ -1,59 +1,11 @@ @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 { + &.oo-ui-flaggedElement-muted { background-color: #f8f9fa; // Base90 AAA - .mw-rcfilters-ui-filterMenuOptionWidget-label-title, - .mw-rcfilters-ui-filterMenuOptionWidget-label-desc { + .mw-rcfilters-ui-itemMenuOptionWidget-label-title, + .mw-rcfilters-ui-itemMenuOptionWidget-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; - } } diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FloatingMenuSelectWidget.less similarity index 88% rename from resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.less rename to resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FloatingMenuSelectWidget.less index 7602465e310..67823c9a59a 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FloatingMenuSelectWidget.less @@ -1,6 +1,6 @@ @import 'mediawiki.mixins'; -.mw-rcfilters-ui-filterFloatingMenuSelectWidget { +.mw-rcfilters-ui-floatingMenuSelectWidget { z-index: auto; max-width: 650px; diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ItemMenuOptionWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ItemMenuOptionWidget.less new file mode 100644 index 00000000000..44c5529636b --- /dev/null +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ItemMenuOptionWidget.less @@ -0,0 +1,51 @@ +@import 'mediawiki.mixins'; + +.mw-rcfilters-ui-itemMenuOptionWidget { + 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; + } + + &.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; + } + } + + &-itemCheckbox { + .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; + } +} diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagItemWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.TagItemWidget.less similarity index 97% rename from resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagItemWidget.less rename to resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.TagItemWidget.less index 0c89660031d..4805f641c4e 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagItemWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.TagItemWidget.less @@ -1,6 +1,6 @@ @import 'mw.rcfilters.mixins'; -.mw-rcfilters-ui-filterTagItemWidget { +.mw-rcfilters-ui-tagItemWidget { // Background and color of the capsule widget need a bit // more specificity to override ooui internals &.oo-ui-flaggedElement-muted.oo-ui-tagItemWidget.oo-ui-widget-enabled { diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js index bda537f1f1b..d235c3991a5 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js @@ -2,7 +2,7 @@ /** * A widget representing a single toggle filter * - * @extends OO.ui.MenuOptionWidget + * @extends mw.rcfilters.ui.ItemMenuOptionWidget * * @constructor * @param {mw.rcfilters.Controller} controller RCFilters controller @@ -10,88 +10,23 @@ * @param {Object} config Configuration object */ mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, model, config ) { - var layout, - $label = $( '
' ) - .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 ) ); + mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, controller, model, config ); - this.checkboxWidget = new mw.rcfilters.ui.CheckboxInputWidget( { - value: this.model.getName(), - selected: this.model.isSelected() - } ); - - $label.append( - $( '
' ) - .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label-title' ) - .append( this.$label ) - ); - if ( this.model.getDescription() ) { - $label.append( - $( '
' ) - .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' } ); - // HACK: Prevent defaults on 'click' for the label so it - // doesn't steal the focus away from the input. This means - // we can continue arrow-movement after we click the label - // and is consistent with the checkbox *itself* also preventing - // defaults on 'click' as well. - layout.$label.on( 'click', false ); this.$element - .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget' ) - .append( - $( '
' ) - .addClass( 'mw-rcfilters-ui-table' ) - .append( - $( '
' ) - .addClass( 'mw-rcfilters-ui-row' ) - .append( - $( '
' ) - .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterMenuOptionWidget-filterCheckbox' ) - .append( layout.$element ), - $( '
' ) - .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterMenuOptionWidget-highlightButton' ) - .append( this.highlightButton.$element ) - ) - ) - ); + .addClass( 'mw-rcfilters-ui-filterMenuOptionWidget' ); }; /* Initialization */ - - OO.inheritClass( mw.rcfilters.ui.FilterMenuOptionWidget, OO.ui.MenuOptionWidget ); + OO.inheritClass( mw.rcfilters.ui.FilterMenuOptionWidget, mw.rcfilters.ui.ItemMenuOptionWidget ); /* Static properties */ @@ -101,10 +36,11 @@ /* Methods */ /** - * Respond to item model update event + * @inheritdoc */ mw.rcfilters.ui.FilterMenuOptionWidget.prototype.onModelUpdate = function () { - this.checkboxWidget.setSelected( this.model.isSelected() ); + // Parent + mw.rcfilters.ui.FilterMenuOptionWidget.parent.prototype.onModelUpdate.call( this ); this.setCurrentMuteState(); }; @@ -117,36 +53,21 @@ }; /** - * Set the current mute state for this item + * Set the current muted view of the widget based on its state */ 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.setFlags( { + 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 ) ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js index d7e5f80d791..8a36eb41928 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js @@ -1,99 +1,32 @@ -( function ( mw, $ ) { +( function ( mw ) { /** * Extend OOUI's FilterTagItemWidget to also display a popup on hover. * * @class - * @extends OO.ui.FilterTagItemWidget - * @mixins OO.ui.mixin.PopupElement + * @extends mw.rcfilters.ui.TagItemWidget * * @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; - this.selected = false; + mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, controller, model, config ); - 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: $( '
' ) - .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 = $( '
' ) - .addClass( 'mw-rcfilters-ui-filterTagItemWidget-highlight' ); - - // Events - this.model.connect( this, { update: 'onModelUpdate' } ); - - // Initialization - this.$overlay.append( this.popup.$element ); this.$element - .addClass( 'mw-rcfilters-ui-filterTagItemWidget' ) - .prepend( this.$highlight ) - .attr( 'aria-haspopup', 'true' ) - .on( 'mouseenter', this.onMouseEnter.bind( this ) ) - .on( 'mouseleave', this.onMouseLeave.bind( this ) ); - - this.setCurrentMuteState(); - this.setHighlightColor(); + .addClass( 'mw-rcfilters-ui-filterTagItemWidget' ); }; /* Initialization */ - OO.inheritClass( mw.rcfilters.ui.FilterTagItemWidget, OO.ui.TagItemWidget ); - OO.mixinClass( mw.rcfilters.ui.FilterTagItemWidget, OO.ui.mixin.PopupElement ); + OO.inheritClass( mw.rcfilters.ui.FilterTagItemWidget, mw.rcfilters.ui.TagItemWidget ); /* 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 + * @inheritdoc */ mw.rcfilters.ui.FilterTagItemWidget.prototype.setCurrentMuteState = function () { this.setFlags( { @@ -105,88 +38,4 @@ invalid: 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 the selected state of this widget - * - * @return {boolean} Tag is selected - */ - mw.rcfilters.ui.FilterTagItemWidget.prototype.isSelected = function () { - return 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 ) ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js index 6fd3585c121..4192aadaa07 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js @@ -338,7 +338,7 @@ * @inheritdoc */ mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createMenuWidget = function ( menuConfig ) { - return new mw.rcfilters.ui.FilterFloatingMenuSelectWidget( + return new mw.rcfilters.ui.FloatingMenuSelectWidget( this.controller, this.model, $.extend( { diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FloatingMenuSelectWidget.js similarity index 71% rename from resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.js rename to resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FloatingMenuSelectWidget.js index 748eea8a8e6..168f7d79b8f 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FloatingMenuSelectWidget.js @@ -11,7 +11,7 @@ * @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 ) { + mw.rcfilters.ui.FloatingMenuSelectWidget = function MwRcfiltersUiFloatingMenuSelectWidget( controller, model, config ) { var header; config = config || {}; @@ -23,16 +23,16 @@ this.$overlay = config.$overlay || this.$element; this.$footer = config.$footer; this.$body = $( '
' ) - .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-body' ); + .addClass( 'mw-rcfilters-ui-floatingMenuSelectWidget-body' ); // Parent - mw.rcfilters.ui.FilterFloatingMenuSelectWidget.parent.call( this, $.extend( { + mw.rcfilters.ui.FloatingMenuSelectWidget.parent.call( this, $.extend( { $autoCloseIgnore: this.$overlay, width: 650 }, config ) ); this.setGroupElement( $( '
' ) - .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-group' ) + .addClass( 'mw-rcfilters-ui-floatingMenuSelectWidget-group' ) ); this.setClippableElement( this.$body ); this.setClippableContainer( this.$element ); @@ -47,11 +47,11 @@ this.noResults = new OO.ui.LabelWidget( { label: mw.msg( 'rcfilters-filterlist-noresults' ), - classes: [ 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-noresults' ] + classes: [ 'mw-rcfilters-ui-floatingMenuSelectWidget-noresults' ] } ); this.$element - .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget' ) + .addClass( 'mw-rcfilters-ui-floatingMenuSelectWidget' ) .append( this.$body .append( header.$element, this.$group, this.noResults.$element ) @@ -60,14 +60,14 @@ if ( this.$footer ) { this.$element.append( this.$footer - .addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-footer' ) + .addClass( 'mw-rcfilters-ui-floatingMenuSelectWidget-footer' ) ); } }; /* Initialize */ - OO.inheritClass( mw.rcfilters.ui.FilterFloatingMenuSelectWidget, OO.ui.FloatingMenuSelectWidget ); + OO.inheritClass( mw.rcfilters.ui.FloatingMenuSelectWidget, OO.ui.FloatingMenuSelectWidget ); /* Events */ @@ -83,7 +83,7 @@ * @fires itemVisibilityChange * @inheritdoc */ - mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.updateItemVisibility = function () { + mw.rcfilters.ui.FloatingMenuSelectWidget.prototype.updateItemVisibility = function () { var i, itemWasHighlighted = false, inputVal = this.$input.val(), @@ -93,7 +93,7 @@ // call it unless the input actually changed if ( this.inputValue !== inputVal ) { // Parent method - mw.rcfilters.ui.FilterFloatingMenuSelectWidget.parent.prototype.updateItemVisibility.call( this ); + mw.rcfilters.ui.FloatingMenuSelectWidget.parent.prototype.updateItemVisibility.call( this ); if ( inputVal !== '' ) { // Highlight the first item in the list @@ -125,7 +125,7 @@ * * @inheritdoc */ - mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.getItemMatcher = function ( s ) { + mw.rcfilters.ui.FloatingMenuSelectWidget.prototype.getItemMatcher = function ( s ) { var results = this.model.findMatches( s, true ); return function ( item ) { @@ -136,7 +136,7 @@ /** * Scroll to the top of the menu */ - mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.scrollToTop = function () { + mw.rcfilters.ui.FloatingMenuSelectWidget.prototype.scrollToTop = function () { this.$body.scrollTop( 0 ); }; }( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ItemMenuOptionWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ItemMenuOptionWidget.js new file mode 100644 index 00000000000..a88d119fa81 --- /dev/null +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ItemMenuOptionWidget.js @@ -0,0 +1,125 @@ +( function ( mw ) { + /** + * A widget representing a base toggle item + * + * @extends OO.ui.MenuOptionWidget + * + * @constructor + * @param {mw.rcfilters.Controller} controller RCFilters controller + * @param {mw.rcfilters.dm.ItemModel} model Item model + * @param {Object} config Configuration object + */ + mw.rcfilters.ui.ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget( controller, model, config ) { + var layout, + $label = $( '
' ) + .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget-label' ); + + config = config || {}; + + this.controller = controller; + this.model = model; + + // Parent + mw.rcfilters.ui.ItemMenuOptionWidget.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( + $( '
' ) + .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget-label-title' ) + .append( this.$label ) + ); + if ( this.model.getDescription() ) { + $label.append( + $( '
' ) + .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget-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' + } ); + + // Events + this.model.connect( this, { update: 'onModelUpdate' } ); + // HACK: Prevent defaults on 'click' for the label so it + // doesn't steal the focus away from the input. This means + // we can continue arrow-movement after we click the label + // and is consistent with the checkbox *itself* also preventing + // defaults on 'click' as well. + layout.$label.on( 'click', false ); + + this.$element + .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget' ) + .append( + $( '
' ) + .addClass( 'mw-rcfilters-ui-table' ) + .append( + $( '
' ) + .addClass( 'mw-rcfilters-ui-row' ) + .append( + $( '
' ) + .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-itemCheckbox' ) + .append( layout.$element ), + $( '
' ) + .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-highlightButton' ) + .append( this.highlightButton.$element ) + ) + ) + ); + }; + + /* Initialization */ + + OO.inheritClass( mw.rcfilters.ui.ItemMenuOptionWidget, OO.ui.MenuOptionWidget ); + + /* Static properties */ + + // We do our own scrolling to top + mw.rcfilters.ui.ItemMenuOptionWidget.static.scrollIntoViewOnSelect = false; + + /* Methods */ + + /** + * Respond to item model update event + */ + mw.rcfilters.ui.ItemMenuOptionWidget.prototype.onModelUpdate = function () { + this.checkboxWidget.setSelected( this.model.isSelected() ); + + this.highlightButton.toggle( this.model.isHighlightEnabled() ); + }; + + /** + * Get the name of this filter + * + * @return {string} Filter name + */ + mw.rcfilters.ui.ItemMenuOptionWidget.prototype.getName = function () { + return this.model.getName(); + }; + + mw.rcfilters.ui.ItemMenuOptionWidget.prototype.getModel = function () { + return this.model; + }; + +}( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.TagItemWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.TagItemWidget.js new file mode 100644 index 00000000000..637dbdce16a --- /dev/null +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.TagItemWidget.js @@ -0,0 +1,183 @@ +( function ( mw, $ ) { + /** + * Extend OOUI's TagItemWidget to also display a popup on hover. + * + * @class + * @extends OO.ui.TagItemWidget + * @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.TagItemWidget = function MwRcfiltersUiTagItemWidget( controller, model, config ) { + // Configuration initialization + config = config || {}; + + this.controller = controller; + this.model = model; + this.selected = false; + + mw.rcfilters.ui.TagItemWidget.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: $( '
' ) + .addClass( 'mw-rcfilters-ui-tagItemWidget-popup-content' ) + .append( this.popupLabel.$element ), + $floatableContainer: this.$element, + classes: [ 'mw-rcfilters-ui-tagItemWidget-popup' ] + } + }, config ) ); + + this.positioned = false; + this.popupTimeoutShow = null; + this.popupTimeoutHide = null; + + this.$highlight = $( '
' ) + .addClass( 'mw-rcfilters-ui-tagItemWidget-highlight' ); + + // Events + this.model.connect( this, { update: 'onModelUpdate' } ); + + // Initialization + this.$overlay.append( this.popup.$element ); + this.$element + .addClass( 'mw-rcfilters-ui-tagItemWidget' ) + .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.TagItemWidget, OO.ui.TagItemWidget ); + OO.mixinClass( mw.rcfilters.ui.TagItemWidget, OO.ui.mixin.PopupElement ); + + /* Methods */ + + /** + * Respond to model update event + */ + mw.rcfilters.ui.TagItemWidget.prototype.onModelUpdate = function () { + this.setCurrentMuteState(); + + this.setHighlightColor(); + }; + + mw.rcfilters.ui.TagItemWidget.prototype.setHighlightColor = function () { + var selectedColor = this.model.isHighlightEnabled() ? this.model.getHighlightColor() : null; + + this.$highlight + .attr( 'data-color', selectedColor ) + .toggleClass( + 'mw-rcfilters-ui-tagItemWidget-highlight-highlighted', + !!selectedColor + ); + }; + + /** + * Set the current mute state for this item + */ + mw.rcfilters.ui.TagItemWidget.prototype.setCurrentMuteState = function () {}; + + /** + * Respond to mouse enter event + */ + mw.rcfilters.ui.TagItemWidget.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.TagItemWidget.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.TagItemWidget.prototype.toggleSelected = function ( isSelected ) { + isSelected = isSelected !== undefined ? isSelected : !this.selected; + + if ( this.selected !== isSelected ) { + this.selected = isSelected; + + this.$element.toggleClass( 'mw-rcfilters-ui-tagItemWidget-selected', this.selected ); + } + }; + + /** + * Get the selected state of this widget + * + * @return {boolean} Tag is selected + */ + mw.rcfilters.ui.TagItemWidget.prototype.isSelected = function () { + return this.selected; + }; + + /** + * Get item name + * + * @return {string} Filter name + */ + mw.rcfilters.ui.TagItemWidget.prototype.getName = function () { + return this.model.getName(); + }; + + /** + * Remove and destroy external elements of this widget + */ + mw.rcfilters.ui.TagItemWidget.prototype.destroy = function () { + // Destroy the popup + this.popup.$element.detach(); + + // Disconnect events + this.model.disconnect( this ); + this.closeButton.disconnect( this ); + }; +}( mediaWiki, jQuery ) );