4e2079dc408ecbbde98960333c61b52a42f1766c
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FilterItem.js
1 ( function ( mw ) {
2 /**
3 * Filter item model
4 *
5 * @extends mw.rcfilters.dm.ItemModel
6 *
7 * @constructor
8 * @param {string} param Filter param name
9 * @param {mw.rcfilters.dm.FilterGroup} groupModel Filter group model
10 * @param {Object} config Configuration object
11 * @cfg {string[]} [excludes=[]] A list of filter names this filter, if
12 * selected, makes inactive.
13 * @cfg {string[]} [subset] Defining the names of filters that are a subset of this filter
14 * @cfg {Object} [conflicts] Defines the conflicts for this filter
15 */
16 mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( param, groupModel, config ) {
17 config = config || {};
18
19 this.groupModel = groupModel;
20
21 // Parent
22 mw.rcfilters.dm.FilterItem.parent.call( this, param, $.extend( {
23 namePrefix: this.groupModel.getNamePrefix()
24 }, config ) );
25 // Mixin constructor
26 OO.EventEmitter.call( this );
27
28 // Interaction definitions
29 this.subset = config.subset || [];
30 this.conflicts = config.conflicts || {};
31 this.superset = [];
32
33 // Interaction states
34 this.included = false;
35 this.conflicted = false;
36 this.fullyCovered = false;
37 };
38
39 /* Initialization */
40
41 OO.inheritClass( mw.rcfilters.dm.FilterItem, mw.rcfilters.dm.ItemModel );
42
43 /* Methods */
44
45 /**
46 * Return the representation of the state of this item.
47 *
48 * @return {Object} State of the object
49 */
50 mw.rcfilters.dm.FilterItem.prototype.getState = function () {
51 return {
52 selected: this.isSelected(),
53 included: this.isIncluded(),
54 conflicted: this.isConflicted(),
55 fullyCovered: this.isFullyCovered()
56 };
57 };
58
59 /**
60 * Get the message for the display area for the currently active conflict
61 *
62 * @private
63 * @return {string} Conflict result message key
64 */
65 mw.rcfilters.dm.FilterItem.prototype.getCurrentConflictResultMessage = function () {
66 var details = {};
67
68 // First look in filter's own conflicts
69 details = this.getConflictDetails( this.getOwnConflicts(), 'globalDescription' );
70 if ( !details.message ) {
71 // Fall back onto conflicts in the group
72 details = this.getConflictDetails( this.getGroupModel().getConflicts(), 'globalDescription' );
73 }
74
75 return details.message;
76 };
77
78 /**
79 * Get the details of the active conflict on this filter
80 *
81 * @private
82 * @param {Object} conflicts Conflicts to examine
83 * @param {string} [key='contextDescription'] Message key
84 * @return {Object} Object with conflict message and conflict items
85 * @return {string} return.message Conflict message
86 * @return {string[]} return.names Conflicting item labels
87 */
88 mw.rcfilters.dm.FilterItem.prototype.getConflictDetails = function ( conflicts, key ) {
89 var group,
90 conflictMessage = '',
91 itemLabels = [];
92
93 key = key || 'contextDescription';
94
95 $.each( conflicts, function ( filterName, conflict ) {
96 if ( !conflict.item.isSelected() ) {
97 return;
98 }
99
100 if ( !conflictMessage ) {
101 conflictMessage = conflict[ key ];
102 group = conflict.group;
103 }
104
105 if ( group === conflict.group ) {
106 itemLabels.push( mw.msg( 'quotation-marks', conflict.item.getLabel() ) );
107 }
108 } );
109
110 return {
111 message: conflictMessage,
112 names: itemLabels
113 };
114
115 };
116
117 /**
118 * @inheritdoc
119 */
120 mw.rcfilters.dm.FilterItem.prototype.getStateMessage = function () {
121 var messageKey, details, superset,
122 affectingItems = [];
123
124 if ( this.isSelected() ) {
125 if ( this.isConflicted() ) {
126 // First look in filter's own conflicts
127 details = this.getConflictDetails( this.getOwnConflicts() );
128 if ( !details.message ) {
129 // Fall back onto conflicts in the group
130 details = this.getConflictDetails( this.getGroupModel().getConflicts() );
131 }
132
133 messageKey = details.message;
134 affectingItems = details.names;
135 } else if ( this.isIncluded() && !this.isHighlighted() ) {
136 // We only show the 'no effect' full-coverage message
137 // if the item is also not highlighted. See T161273
138 superset = this.getSuperset();
139 // For this message we need to collect the affecting superset
140 affectingItems = this.getGroupModel().getSelectedItems( this )
141 .filter( function ( item ) {
142 return superset.indexOf( item.getName() ) !== -1;
143 } )
144 .map( function ( item ) {
145 return mw.msg( 'quotation-marks', item.getLabel() );
146 } );
147
148 messageKey = 'rcfilters-state-message-subset';
149 } else if ( this.isFullyCovered() && !this.isHighlighted() ) {
150 affectingItems = this.getGroupModel().getSelectedItems( this )
151 .map( function ( item ) {
152 return mw.msg( 'quotation-marks', item.getLabel() );
153 } );
154
155 messageKey = 'rcfilters-state-message-fullcoverage';
156 }
157 }
158
159 if ( messageKey ) {
160 // Build message
161 return mw.msg(
162 messageKey,
163 mw.language.listToText( affectingItems ),
164 affectingItems.length
165 );
166 }
167
168 // Display description
169 return this.getDescription();
170 };
171
172 /**
173 * Get the model of the group this filter belongs to
174 *
175 * @return {mw.rcfilters.dm.FilterGroup} Filter group model
176 */
177 mw.rcfilters.dm.FilterItem.prototype.getGroupModel = function () {
178 return this.groupModel;
179 };
180
181 /**
182 * Get the group name this filter belongs to
183 *
184 * @return {string} Filter group name
185 */
186 mw.rcfilters.dm.FilterItem.prototype.getGroupName = function () {
187 return this.groupModel.getName();
188 };
189
190 /**
191 * Get filter subset
192 * This is a list of filter names that are defined to be included
193 * when this filter is selected.
194 *
195 * @return {string[]} Filter subset
196 */
197 mw.rcfilters.dm.FilterItem.prototype.getSubset = function () {
198 return this.subset;
199 };
200
201 /**
202 * Get filter superset
203 * This is a generated list of filters that define this filter
204 * to be included when either of them is selected.
205 *
206 * @return {string[]} Filter superset
207 */
208 mw.rcfilters.dm.FilterItem.prototype.getSuperset = function () {
209 return this.superset;
210 };
211
212 /**
213 * Check whether the filter is currently in a conflict state
214 *
215 * @return {boolean} Filter is in conflict state
216 */
217 mw.rcfilters.dm.FilterItem.prototype.isConflicted = function () {
218 return this.conflicted;
219 };
220
221 /**
222 * Check whether the filter is currently in an already included subset
223 *
224 * @return {boolean} Filter is in an already-included subset
225 */
226 mw.rcfilters.dm.FilterItem.prototype.isIncluded = function () {
227 return this.included;
228 };
229
230 /**
231 * Check whether the filter is currently fully covered
232 *
233 * @return {boolean} Filter is in fully-covered state
234 */
235 mw.rcfilters.dm.FilterItem.prototype.isFullyCovered = function () {
236 return this.fullyCovered;
237 };
238
239 /**
240 * Get all conflicts associated with this filter or its group
241 *
242 * Conflict object is set up by filter name keys and conflict
243 * definition. For example:
244 * {
245 * filterName: {
246 * filter: filterName,
247 * group: group1,
248 * label: itemLabel,
249 * item: itemModel
250 * }
251 * filterName2: {
252 * filter: filterName2,
253 * group: group2
254 * label: itemLabel2,
255 * item: itemModel2
256 * }
257 * }
258 *
259 * @return {Object} Filter conflicts
260 */
261 mw.rcfilters.dm.FilterItem.prototype.getConflicts = function () {
262 return $.extend( {}, this.conflicts, this.getGroupModel().getConflicts() );
263 };
264
265 /**
266 * Get the conflicts associated with this filter
267 *
268 * @return {Object} Filter conflicts
269 */
270 mw.rcfilters.dm.FilterItem.prototype.getOwnConflicts = function () {
271 return this.conflicts;
272 };
273
274 /**
275 * Set conflicts for this filter. See #getConflicts for the expected
276 * structure of the definition.
277 *
278 * @param {Object} conflicts Conflicts for this filter
279 */
280 mw.rcfilters.dm.FilterItem.prototype.setConflicts = function ( conflicts ) {
281 this.conflicts = conflicts || {};
282 };
283
284 /**
285 * Set filter superset
286 *
287 * @param {string[]} superset Filter superset
288 */
289 mw.rcfilters.dm.FilterItem.prototype.setSuperset = function ( superset ) {
290 this.superset = superset || [];
291 };
292
293 /**
294 * Set filter subset
295 *
296 * @param {string[]} subset Filter subset
297 */
298 mw.rcfilters.dm.FilterItem.prototype.setSubset = function ( subset ) {
299 this.subset = subset || [];
300 };
301
302 /**
303 * Check whether a filter exists in the subset list for this filter
304 *
305 * @param {string} filterName Filter name
306 * @return {boolean} Filter name is in the subset list
307 */
308 mw.rcfilters.dm.FilterItem.prototype.existsInSubset = function ( filterName ) {
309 return this.subset.indexOf( filterName ) > -1;
310 };
311
312 /**
313 * Check whether this item has a potential conflict with the given item
314 *
315 * This checks whether the given item is in the list of conflicts of
316 * the current item, but makes no judgment about whether the conflict
317 * is currently at play (either one of the items may not be selected)
318 *
319 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
320 * @return {boolean} This item has a conflict with the given item
321 */
322 mw.rcfilters.dm.FilterItem.prototype.existsInConflicts = function ( filterItem ) {
323 return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
324 };
325
326 /**
327 * Set the state of this filter as being conflicted
328 * (This means any filters in its conflicts are selected)
329 *
330 * @param {boolean} [conflicted] Filter is in conflict state
331 * @fires update
332 */
333 mw.rcfilters.dm.FilterItem.prototype.toggleConflicted = function ( conflicted ) {
334 conflicted = conflicted === undefined ? !this.conflicted : conflicted;
335
336 if ( this.conflicted !== conflicted ) {
337 this.conflicted = conflicted;
338 this.emit( 'update' );
339 }
340 };
341
342 /**
343 * Set the state of this filter as being already included
344 * (This means any filters in its superset are selected)
345 *
346 * @param {boolean} [included] Filter is included as part of a subset
347 * @fires update
348 */
349 mw.rcfilters.dm.FilterItem.prototype.toggleIncluded = function ( included ) {
350 included = included === undefined ? !this.included : included;
351
352 if ( this.included !== included ) {
353 this.included = included;
354 this.emit( 'update' );
355 }
356 };
357
358 /**
359 * Toggle the fully covered state of the item
360 *
361 * @param {boolean} [isFullyCovered] Filter is fully covered
362 * @fires update
363 */
364 mw.rcfilters.dm.FilterItem.prototype.toggleFullyCovered = function ( isFullyCovered ) {
365 isFullyCovered = isFullyCovered === undefined ? !this.fullycovered : isFullyCovered;
366
367 if ( this.fullyCovered !== isFullyCovered ) {
368 this.fullyCovered = isFullyCovered;
369 this.emit( 'update' );
370 }
371 };
372 }( mediaWiki ) );