1 const ItemModel = require( './ItemModel.js' );
6 * @class mw.rcfilters.dm.FilterItem
8 * @extends mw.rcfilters.dm.ItemModel
10 * @param {string} param Filter param name
11 * @param {mw.rcfilters.dm.FilterGroup} groupModel Filter group model
12 * @param {Object} config Configuration object
13 * @param {string[]} [config.excludes=[]] A list of filter names this filter, if
14 * selected, makes inactive.
15 * @param {string[]} [config.subset] Defining the names of filters that are a subset of this filter
16 * @param {Object} [config.conflicts] Defines the conflicts for this filter
17 * @param {boolean} [config.visible=true] The visibility of the group
19 const FilterItem = function MwRcfiltersDmFilterItem( param, groupModel, config ) {
20 config = config || {};
22 this.groupModel = groupModel;
25 FilterItem.super.call( this, param, Object.assign( {
26 namePrefix: this.groupModel.getNamePrefix()
29 OO.EventEmitter.call( this );
31 // Interaction definitions
32 this.subset = config.subset || [];
33 this.conflicts = config.conflicts || {};
35 this.visible = config.visible === undefined ? true : !!config.visible;
38 this.included = false;
39 this.conflicted = false;
40 this.fullyCovered = false;
45 OO.inheritClass( FilterItem, ItemModel );
50 * Return the representation of the state of this item.
52 * @return {Object} State of the object
54 FilterItem.prototype.getState = function () {
56 selected: this.isSelected(),
57 included: this.isIncluded(),
58 conflicted: this.isConflicted(),
59 fullyCovered: this.isFullyCovered()
64 * Get the message for the display area for the currently active conflict
66 * @return {string} Conflict result message key
68 FilterItem.prototype.getCurrentConflictResultMessage = function () {
71 // First look in filter's own conflicts
72 details = this.getConflictDetails( this.getOwnConflicts(), 'globalDescription' );
73 if ( !details.message ) {
74 // Fall back onto conflicts in the group
75 details = this.getConflictDetails( this.getGroupModel().getConflicts(), 'globalDescription' );
78 return details.message;
82 * Get the details of the active conflict on this filter
85 * @param {Object} conflicts Conflicts to examine
86 * @param {string} [key='contextDescription'] Message key
87 * @return {Object} Object with conflict message and conflict items
88 * @return {string} return.message Conflict message
89 * @return {string[]} return.names Conflicting item labels
91 FilterItem.prototype.getConflictDetails = function ( conflicts, key ) {
94 const itemLabels = [];
96 key = key || 'contextDescription';
98 // eslint-disable-next-line no-jquery/no-each-util
99 $.each( conflicts, ( filterName, conflict ) => {
100 if ( !conflict.item.isSelected() ) {
104 if ( !conflictMessage ) {
105 conflictMessage = conflict[ key ];
106 group = conflict.group;
109 if ( group === conflict.group ) {
110 itemLabels.push( mw.msg( 'quotation-marks', conflict.item.getLabel() ) );
115 message: conflictMessage,
124 FilterItem.prototype.getStateMessage = function () {
125 let messageKey, details, superset,
128 if ( this.isSelected() ) {
129 if ( this.isConflicted() ) {
130 // First look in filter's own conflicts
131 details = this.getConflictDetails( this.getOwnConflicts() );
132 if ( !details.message ) {
133 // Fall back onto conflicts in the group
134 details = this.getConflictDetails( this.getGroupModel().getConflicts() );
137 messageKey = details.message;
138 affectingItems = details.names;
139 } else if ( this.isIncluded() && !this.isHighlighted() ) {
140 // We only show the 'no effect' full-coverage message
141 // if the item is also not highlighted. See T161273
142 superset = this.getSuperset();
143 // For this message we need to collect the affecting superset
144 affectingItems = this.getGroupModel().findSelectedItems( this )
145 .filter( ( item ) => superset.indexOf( item.getName() ) !== -1 )
146 .map( ( item ) => mw.msg( 'quotation-marks', item.getLabel() ) );
148 messageKey = 'rcfilters-state-message-subset';
149 } else if ( this.isFullyCovered() && !this.isHighlighted() ) {
150 affectingItems = this.getGroupModel().findSelectedItems( this )
151 .map( ( item ) => mw.msg( 'quotation-marks', item.getLabel() ) );
153 messageKey = 'rcfilters-state-message-fullcoverage';
159 // The following messages are used here:
160 // * rcfilters-state-message-subset
161 // * rcfilters-state-message-fullcoverage
162 // * conflict.message values...
165 mw.language.listToText( affectingItems ),
166 affectingItems.length
170 // Display description
171 return this.getDescription();
175 * Get the model of the group this filter belongs to
178 * @return {mw.rcfilters.dm.FilterGroup} Filter group model
180 FilterItem.prototype.getGroupModel = function () {
181 return this.groupModel;
185 * Get the group name this filter belongs to
187 * @return {string} Filter group name
189 FilterItem.prototype.getGroupName = function () {
190 return this.groupModel.getName();
195 * This is a list of filter names that are defined to be included
196 * when this filter is selected.
198 * @return {string[]} Filter subset
200 FilterItem.prototype.getSubset = function () {
205 * Get filter superset
206 * This is a generated list of filters that define this filter
207 * to be included when either of them is selected.
209 * @return {string[]} Filter superset
211 FilterItem.prototype.getSuperset = function () {
212 return this.superset;
216 * Check whether the filter is currently in a conflict state
218 * @return {boolean} Filter is in conflict state
220 FilterItem.prototype.isConflicted = function () {
221 return this.conflicted;
225 * Check whether the filter is currently in an already included subset
227 * @return {boolean} Filter is in an already-included subset
229 FilterItem.prototype.isIncluded = function () {
230 return this.included;
234 * Check whether the filter is currently fully covered
236 * @return {boolean} Filter is in fully-covered state
238 FilterItem.prototype.isFullyCovered = function () {
239 return this.fullyCovered;
243 * Get all conflicts associated with this filter or its group
245 * Conflict object is set up by filter name keys and conflict
246 * definition. For example:
250 * filter: filterName,
256 * filter: filterName2,
263 * @return {Object} Filter conflicts
265 FilterItem.prototype.getConflicts = function () {
266 return Object.assign( {}, this.conflicts, this.getGroupModel().getConflicts() );
270 * Get the conflicts associated with this filter
272 * @return {Object} Filter conflicts
274 FilterItem.prototype.getOwnConflicts = function () {
275 return this.conflicts;
279 * Set conflicts for this filter. See #getConflicts for the expected
280 * structure of the definition.
282 * @param {Object} conflicts Conflicts for this filter
284 FilterItem.prototype.setConflicts = function ( conflicts ) {
285 this.conflicts = conflicts || {};
289 * Set filter superset
291 * @param {string[]} superset Filter superset
293 FilterItem.prototype.setSuperset = function ( superset ) {
294 this.superset = superset || [];
300 * @param {string[]} subset Filter subset
302 FilterItem.prototype.setSubset = function ( subset ) {
303 this.subset = subset || [];
307 * Check whether a filter exists in the subset list for this filter
309 * @param {string} filterName Filter name
310 * @return {boolean} Filter name is in the subset list
312 FilterItem.prototype.existsInSubset = function ( filterName ) {
313 return this.subset.indexOf( filterName ) > -1;
317 * Check whether this item has a potential conflict with the given item
319 * This checks whether the given item is in the list of conflicts of
320 * the current item, but makes no judgment about whether the conflict
321 * is currently at play (either one of the items may not be selected)
323 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
324 * @return {boolean} This item has a conflict with the given item
326 FilterItem.prototype.existsInConflicts = function ( filterItem ) {
327 return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
331 * Set the state of this filter as being conflicted
332 * (This means any filters in its conflicts are selected)
334 * @param {boolean} [conflicted] Filter is in conflict state
337 FilterItem.prototype.toggleConflicted = function ( conflicted ) {
338 conflicted = conflicted === undefined ? !this.conflicted : conflicted;
340 if ( this.conflicted !== conflicted ) {
341 this.conflicted = conflicted;
342 this.emit( 'update' );
347 * Set the state of this filter as being already included
348 * (This means any filters in its superset are selected)
350 * @param {boolean} [included] Filter is included as part of a subset
353 FilterItem.prototype.toggleIncluded = function ( included ) {
354 included = included === undefined ? !this.included : included;
356 if ( this.included !== included ) {
357 this.included = included;
358 this.emit( 'update' );
363 * Toggle the fully covered state of the item
365 * @param {boolean} [isFullyCovered] Filter is fully covered
368 FilterItem.prototype.toggleFullyCovered = function ( isFullyCovered ) {
369 isFullyCovered = isFullyCovered === undefined ? !this.fullycovered : isFullyCovered;
371 if ( this.fullyCovered !== isFullyCovered ) {
372 this.fullyCovered = isFullyCovered;
373 this.emit( 'update' );
378 * Toggle the visibility of this item
380 * @param {boolean} [isVisible] Item is visible
382 FilterItem.prototype.toggleVisible = function ( isVisible ) {
383 isVisible = isVisible === undefined ? !this.visible : !!isVisible;
385 if ( this.visible !== isVisible ) {
386 this.visible = isVisible;
387 this.emit( 'update' );
392 * Check whether the item is visible
394 * @return {boolean} Item is visible
396 FilterItem.prototype.isVisible = function () {
400 module.exports = FilterItem;