Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / mediawiki.rcfilters / dm / SavedQueriesModel.js
blob6276dd6e11c8f14f9f6d7deeae8a28f1677c3f22
1 const SavedQueryItemModel = require( './SavedQueryItemModel.js' );
3 /**
4  * View model for saved queries.
5  *
6  * @class mw.rcfilters.dm.SavedQueriesModel
7  * @ignore
8  * @mixes OO.EventEmitter
9  * @mixes OO.EmitterList
10  *
11  * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters model
12  * @param {Object} [config] Configuration options
13  * @param {string} [config.default] Default query ID
14  */
15 const SavedQueriesModel = function MwRcfiltersDmSavedQueriesModel( filtersModel, config ) {
16         config = config || {};
18         // Mixin constructor
19         OO.EventEmitter.call( this );
20         OO.EmitterList.call( this );
22         this.default = config.default;
23         this.filtersModel = filtersModel;
24         this.converted = false;
26         // Events
27         this.aggregate( { update: 'itemUpdate' } );
30 /* Initialization */
32 OO.initClass( SavedQueriesModel );
33 OO.mixinClass( SavedQueriesModel, OO.EventEmitter );
34 OO.mixinClass( SavedQueriesModel, OO.EmitterList );
36 /* Events */
38 /**
39  * Model is initialized.
40  *
41  * @event initialize
42  * @ignore
43  */
45 /**
46  * An item has changed.
47  *
48  * @event itemUpdate
49  * @param {mw.rcfilters.dm.SavedQueryItemModel} Changed item
50  * @ignore
51  */
53 /**
54  * The default has changed.
55  *
56  * @event default
57  * @param {string} New default ID
58  * @ignore
59  */
61 /* Methods */
63 /**
64  * Initialize the saved queries model by reading it from the user's settings.
65  * The structure of the saved queries is:
66  * {
67  *    version: (string) Version number; if version 2, the query represents
68  *             parameters. Otherwise, the older version represented filters
69  *             and needs to be readjusted,
70  *    default: (string) Query ID
71  *    queries:{
72  *       query_id_1: {
73  *          data:{
74  *             filters: (Object) Minimal definition of the filters
75  *             highlights: (Object) Definition of the highlights
76  *          },
77  *          label: (optional) Name of this query
78  *       }
79  *    }
80  * }
81  *
82  * @param {Object} [savedQueries] An object with the saved queries with
83  *  the above structure.
84  * @fires initialize
85  */
86 SavedQueriesModel.prototype.initialize = function ( savedQueries ) {
87         savedQueries = savedQueries || {};
89         this.clearItems();
90         this.default = null;
91         this.converted = false;
93         if ( savedQueries.version !== '2' ) {
94                 // Old version dealt with filter names. We need to migrate to the new structure
95                 // The new structure:
96                 // {
97                 //   version: (string) '2',
98                 //   default: (string) Query ID,
99                 //   queries: {
100                 //     query_id: {
101                 //       label: (string) Name of the query
102                 //       data: {
103                 //         params: (object) Representing all the parameter states
104                 //         highlights: (object) Representing all the filter highlight states
105                 //     }
106                 //   }
107                 // }
108                 // eslint-disable-next-line no-jquery/no-each-util
109                 $.each( savedQueries.queries || {}, ( id, obj ) => {
110                         if ( obj.data && obj.data.filters ) {
111                                 obj.data = this.convertToParameters( obj.data );
112                         }
113                 } );
115                 this.converted = true;
116                 savedQueries.version = '2';
117         }
119         // Initialize the query items
120         // eslint-disable-next-line no-jquery/no-each-util
121         $.each( savedQueries.queries || {}, ( id, obj ) => {
122                 const normalizedData = obj.data,
123                         isDefault = String( savedQueries.default ) === String( id );
125                 if ( normalizedData && normalizedData.params ) {
126                         // Backwards-compat fix: Remove sticky parameters from
127                         // the given data, if they exist
128                         normalizedData.params = this.filtersModel.removeStickyParams( normalizedData.params );
130                         // Correct the invert state for effective selection
131                         if ( normalizedData.params.invert && !normalizedData.params.namespace ) {
132                                 delete normalizedData.params.invert;
133                         }
135                         this.cleanupHighlights( normalizedData );
137                         id = String( id );
139                         // Skip the addNewQuery method because we don't want to unnecessarily manipulate
140                         // the given saved queries unless we literally intend to (like in backwards compat fixes)
141                         // And the addNewQuery method also uses a minimization routine that checks for the
142                         // validity of items and minimizes the query. This isn't necessary for queries loaded
143                         // from the backend, and has the risk of removing values if they're temporarily
144                         // invalid (example: if we temporarily removed a cssClass from a filter in the backend)
145                         this.addItems( [
146                                 new SavedQueryItemModel(
147                                         id,
148                                         obj.label,
149                                         normalizedData,
150                                         { default: isDefault }
151                                 )
152                         ] );
154                         if ( isDefault ) {
155                                 this.default = id;
156                         }
157                 }
158         } );
160         this.emit( 'initialize' );
164  * Clean up highlight parameters.
165  * 'highlight' used to be stored, it's not inferred based on the presence of absence of
166  * filter colors.
168  * @param {Object} data Saved query data
169  */
170 SavedQueriesModel.prototype.cleanupHighlights = function ( data ) {
171         if (
172                 data.params.highlight === '0' &&
173                 data.highlights && Object.keys( data.highlights ).length
174         ) {
175                 data.highlights = {};
176         }
177         delete data.params.highlight;
181  * Convert from representation of filters to representation of parameters
183  * @param {Object} data Query data
184  * @return {Object} New converted query data
185  */
186 SavedQueriesModel.prototype.convertToParameters = function ( data ) {
187         const newData = {},
188                 defaultFilters = this.filtersModel.getFiltersFromParameters( this.filtersModel.getDefaultParams() ),
189                 fullFilterRepresentation = $.extend( true, {}, defaultFilters, data.filters ),
190                 highlightEnabled = data.highlights.highlight;
192         delete data.highlights.highlight;
194         // Filters
195         newData.params = this.filtersModel.getMinimizedParamRepresentation(
196                 this.filtersModel.getParametersFromFilters( fullFilterRepresentation )
197         );
199         // Highlights: appending _color to keys
200         newData.highlights = {};
201         // eslint-disable-next-line no-jquery/no-each-util
202         $.each( data.highlights, ( highlightedFilterName, value ) => {
203                 if ( value ) {
204                         newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
205                 }
206         } );
208         // Add highlight
209         newData.params.highlight = String( Number( highlightEnabled || 0 ) );
211         return newData;
215  * Add a query item
217  * @param {string} label Label for the new query
218  * @param {Object} fulldata Full data representation for the new query, combining highlights and filters
219  * @param {boolean} isDefault Item is default
220  * @param {string} [id] Query ID, if exists. If this isn't given, a random
221  *  new ID will be created.
222  * @return {string} ID of the newly added query
223  */
224 SavedQueriesModel.prototype.addNewQuery = function ( label, fulldata, isDefault, id ) {
225         const normalizedData = { params: {}, highlights: {} },
226                 highlightParamNames = Object.keys( this.filtersModel.getEmptyHighlightParameters() ),
227                 randomID = String( id || Date.now() ),
228                 data = this.filtersModel.getMinimizedParamRepresentation( fulldata );
230         // Split highlight/params
231         // eslint-disable-next-line no-jquery/no-each-util
232         $.each( data, ( param, value ) => {
233                 if ( param !== 'highlight' && highlightParamNames.indexOf( param ) > -1 ) {
234                         normalizedData.highlights[ param ] = value;
235                 } else {
236                         normalizedData.params[ param ] = value;
237                 }
238         } );
240         // Correct the invert state for effective selection
241         if ( normalizedData.params.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
242                 delete normalizedData.params.invert;
243         }
244         // Correct the inverttags state for effective selection
245         if ( normalizedData.params.inverttags && !this.filtersModel.areTagsEffectivelyInverted() ) {
246                 delete normalizedData.params.inverttags;
247         }
249         // Add item
250         this.addItems( [
251                 new SavedQueryItemModel(
252                         randomID,
253                         label,
254                         normalizedData,
255                         { default: isDefault }
256                 )
257         ] );
259         if ( isDefault ) {
260                 this.setDefault( randomID );
261         }
263         return randomID;
267  * Remove query from model
269  * @param {string} queryID Query ID
270  */
271 SavedQueriesModel.prototype.removeQuery = function ( queryID ) {
272         const query = this.getItemByID( queryID );
274         if ( query ) {
275                 // Check if this item was the default
276                 if ( String( this.getDefault() ) === String( queryID ) ) {
277                         // Nulify the default
278                         this.setDefault( null );
279                 }
281                 this.removeItems( [ query ] );
282         }
286  * Get an item that matches the requested query
288  * @param {Object} fullQueryComparison Object representing all filters and highlights to compare
289  * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model
290  * @ignore
291  */
292 SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) {
293         // Minimize before comparison
294         fullQueryComparison = this.filtersModel.getMinimizedParamRepresentation( fullQueryComparison );
296         // Correct the invert state for effective selection
297         if ( fullQueryComparison.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
298                 delete fullQueryComparison.invert;
299         }
301         return this.getItems().filter( ( item ) => OO.compare(
302                 item.getCombinedData(),
303                 fullQueryComparison
304         ) )[ 0 ];
308  * Get query by its identifier
310  * @ignore
311  * @param {string} queryID Query identifier
312  * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching
313  *  the search. Undefined if not found.
314  */
315 SavedQueriesModel.prototype.getItemByID = function ( queryID ) {
316         return this.getItems().filter( ( item ) => item.getID() === queryID )[ 0 ];
320  * Get the full data representation of the default query, if it exists
322  * @return {Object|null} Representation of the default params if exists.
323  *  Null if default doesn't exist or if the user is not logged in.
324  */
325 SavedQueriesModel.prototype.getDefaultParams = function () {
326         return ( !mw.user.isAnon() && this.getItemParams( this.getDefault() ) ) || {};
330  * Get a full parameter representation of an item data
332  * @param  {Object} queryID Query ID
333  * @return {Object} Parameter representation
334  */
335 SavedQueriesModel.prototype.getItemParams = function ( queryID ) {
336         const item = this.getItemByID( queryID ),
337                 data = item ? item.getData() : {};
339         return !$.isEmptyObject( data ) ? this.buildParamsFromData( data ) : {};
343  * Build a full parameter representation given item data and model sticky values state
345  * @param  {Object} data Item data
346  * @return {Object} Full param representation
347  */
348 SavedQueriesModel.prototype.buildParamsFromData = function ( data ) {
349         data = data || {};
350         // Return parameter representation
351         return this.filtersModel.getMinimizedParamRepresentation( $.extend( true, {},
352                 data.params,
353                 data.highlights
354         ) );
358  * Get the object representing the state of the entire model and items
360  * @return {Object} Object representing the state of the model and items
361  */
362 SavedQueriesModel.prototype.getState = function () {
363         const obj = { queries: {}, version: '2' };
365         // Translate the items to the saved object
366         this.getItems().forEach( ( item ) => {
367                 obj.queries[ item.getID() ] = item.getState();
368         } );
370         if ( this.getDefault() ) {
371                 obj.default = this.getDefault();
372         }
374         return obj;
378  * Set a default query. Null to unset default.
380  * @param {string} itemID Query identifier
381  * @fires default
382  */
383 SavedQueriesModel.prototype.setDefault = function ( itemID ) {
384         if ( this.default !== itemID ) {
385                 this.default = itemID;
387                 // Set for individual itens
388                 this.getItems().forEach( ( item ) => {
389                         item.toggleDefault( item.getID() === itemID );
390                 } );
392                 this.emit( 'default', itemID );
393         }
397  * Get the default query ID
399  * @return {string} Default query identifier
400  */
401 SavedQueriesModel.prototype.getDefault = function () {
402         return this.default;
406  * Check if the saved queries were converted
408  * @return {boolean} Saved queries were converted from the previous
409  *  version to the new version
410  */
411 SavedQueriesModel.prototype.isConverted = function () {
412         return this.converted;
415 module.exports = SavedQueriesModel;