Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / mediawiki.rcfilters / dm / FilterItem.js
blob2f0ab65575402fbc6fb1ccedec159ee20d0b4241
1 const ItemModel = require( './ItemModel.js' );
3 /**
4  * Filter item model.
5  *
6  * @class mw.rcfilters.dm.FilterItem
7  * @ignore
8  * @extends mw.rcfilters.dm.ItemModel
9  *
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
18  */
19 const FilterItem = function MwRcfiltersDmFilterItem( param, groupModel, config ) {
20         config = config || {};
22         this.groupModel = groupModel;
24         // Parent
25         FilterItem.super.call( this, param, Object.assign( {
26                 namePrefix: this.groupModel.getNamePrefix()
27         }, config ) );
28         // Mixin constructor
29         OO.EventEmitter.call( this );
31         // Interaction definitions
32         this.subset = config.subset || [];
33         this.conflicts = config.conflicts || {};
34         this.superset = [];
35         this.visible = config.visible === undefined ? true : !!config.visible;
37         // Interaction states
38         this.included = false;
39         this.conflicted = false;
40         this.fullyCovered = false;
43 /* Initialization */
45 OO.inheritClass( FilterItem, ItemModel );
47 /* Methods */
49 /**
50  * Return the representation of the state of this item.
51  *
52  * @return {Object} State of the object
53  */
54 FilterItem.prototype.getState = function () {
55         return {
56                 selected: this.isSelected(),
57                 included: this.isIncluded(),
58                 conflicted: this.isConflicted(),
59                 fullyCovered: this.isFullyCovered()
60         };
63 /**
64  * Get the message for the display area for the currently active conflict
65  *
66  * @return {string} Conflict result message key
67  */
68 FilterItem.prototype.getCurrentConflictResultMessage = function () {
69         let details;
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' );
76         }
78         return details.message;
81 /**
82  * Get the details of the active conflict on this filter
83  *
84  * @private
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
90  */
91 FilterItem.prototype.getConflictDetails = function ( conflicts, key ) {
92         let group,
93                 conflictMessage = '';
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() ) {
101                         return;
102                 }
104                 if ( !conflictMessage ) {
105                         conflictMessage = conflict[ key ];
106                         group = conflict.group;
107                 }
109                 if ( group === conflict.group ) {
110                         itemLabels.push( mw.msg( 'quotation-marks', conflict.item.getLabel() ) );
111                 }
112         } );
114         return {
115                 message: conflictMessage,
116                 names: itemLabels
117         };
122  * @inheritdoc
123  */
124 FilterItem.prototype.getStateMessage = function () {
125         let messageKey, details, superset,
126                 affectingItems = [];
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() );
135                         }
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';
154                 }
155         }
157         if ( messageKey ) {
158                 // Build message
159                 // The following messages are used here:
160                 // * rcfilters-state-message-subset
161                 // * rcfilters-state-message-fullcoverage
162                 // * conflict.message values...
163                 return mw.msg(
164                         messageKey,
165                         mw.language.listToText( affectingItems ),
166                         affectingItems.length
167                 );
168         }
170         // Display description
171         return this.getDescription();
175  * Get the model of the group this filter belongs to
177  * @ignore
178  * @return {mw.rcfilters.dm.FilterGroup} Filter group model
179  */
180 FilterItem.prototype.getGroupModel = function () {
181         return this.groupModel;
185  * Get the group name this filter belongs to
187  * @return {string} Filter group name
188  */
189 FilterItem.prototype.getGroupName = function () {
190         return this.groupModel.getName();
194  * Get filter subset
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
199  */
200 FilterItem.prototype.getSubset = function () {
201         return this.subset;
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
210  */
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
219  */
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
228  */
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
237  */
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:
248  *  {
249  *      filterName: {
250  *          filter: filterName,
251  *          group: group1,
252  *          label: itemLabel,
253  *          item: itemModel
254  *      }
255  *      filterName2: {
256  *          filter: filterName2,
257  *          group: group2
258  *          label: itemLabel2,
259  *          item: itemModel2
260  *      }
261  *  }
263  * @return {Object} Filter conflicts
264  */
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
273  */
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
283  */
284 FilterItem.prototype.setConflicts = function ( conflicts ) {
285         this.conflicts = conflicts || {};
289  * Set filter superset
291  * @param {string[]} superset Filter superset
292  */
293 FilterItem.prototype.setSuperset = function ( superset ) {
294         this.superset = superset || [];
298  * Set filter subset
300  * @param {string[]} subset Filter subset
301  */
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
311  */
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
325  */
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
335  * @fires update
336  */
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' );
343         }
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
351  * @fires update
352  */
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' );
359         }
363  * Toggle the fully covered state of the item
365  * @param {boolean} [isFullyCovered] Filter is fully covered
366  * @fires update
367  */
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' );
374         }
378  * Toggle the visibility of this item
380  * @param {boolean} [isVisible] Item is visible
381  */
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' );
388         }
392  * Check whether the item is visible
394  * @return {boolean} Item is visible
395  */
396 FilterItem.prototype.isVisible = function () {
397         return this.visible;
400 module.exports = FilterItem;