1 const SavedQueryItemModel = require( './SavedQueryItemModel.js' );
4 * View model for saved queries.
6 * @class mw.rcfilters.dm.SavedQueriesModel
8 * @mixes OO.EventEmitter
9 * @mixes OO.EmitterList
11 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters model
12 * @param {Object} [config] Configuration options
13 * @param {string} [config.default] Default query ID
15 const SavedQueriesModel = function MwRcfiltersDmSavedQueriesModel( filtersModel, config ) {
16 config = config || {};
19 OO.EventEmitter.call( this );
20 OO.EmitterList.call( this );
22 this.default = config.default;
23 this.filtersModel = filtersModel;
24 this.converted = false;
27 this.aggregate( { update: 'itemUpdate' } );
32 OO.initClass( SavedQueriesModel );
33 OO.mixinClass( SavedQueriesModel, OO.EventEmitter );
34 OO.mixinClass( SavedQueriesModel, OO.EmitterList );
39 * Model is initialized.
46 * An item has changed.
49 * @param {mw.rcfilters.dm.SavedQueryItemModel} Changed item
54 * The default has changed.
57 * @param {string} New default ID
64 * Initialize the saved queries model by reading it from the user's settings.
65 * The structure of the saved queries is:
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
74 * filters: (Object) Minimal definition of the filters
75 * highlights: (Object) Definition of the highlights
77 * label: (optional) Name of this query
82 * @param {Object} [savedQueries] An object with the saved queries with
83 * the above structure.
86 SavedQueriesModel.prototype.initialize = function ( savedQueries ) {
87 savedQueries = savedQueries || {};
91 this.converted = false;
93 if ( savedQueries.version !== '2' ) {
94 // Old version dealt with filter names. We need to migrate to the new structure
97 // version: (string) '2',
98 // default: (string) Query ID,
101 // label: (string) Name of the query
103 // params: (object) Representing all the parameter states
104 // highlights: (object) Representing all the filter highlight states
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 );
115 this.converted = true;
116 savedQueries.version = '2';
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;
135 this.cleanupHighlights( normalizedData );
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)
146 new SavedQueryItemModel(
150 { default: isDefault }
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
168 * @param {Object} data Saved query data
170 SavedQueriesModel.prototype.cleanupHighlights = function ( data ) {
172 data.params.highlight === '0' &&
173 data.highlights && Object.keys( data.highlights ).length
175 data.highlights = {};
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
186 SavedQueriesModel.prototype.convertToParameters = function ( data ) {
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;
195 newData.params = this.filtersModel.getMinimizedParamRepresentation(
196 this.filtersModel.getParametersFromFilters( fullFilterRepresentation )
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 ) => {
204 newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
209 newData.params.highlight = String( Number( highlightEnabled || 0 ) );
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
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;
236 normalizedData.params[ param ] = value;
240 // Correct the invert state for effective selection
241 if ( normalizedData.params.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
242 delete normalizedData.params.invert;
244 // Correct the inverttags state for effective selection
245 if ( normalizedData.params.inverttags && !this.filtersModel.areTagsEffectivelyInverted() ) {
246 delete normalizedData.params.inverttags;
251 new SavedQueryItemModel(
255 { default: isDefault }
260 this.setDefault( randomID );
267 * Remove query from model
269 * @param {string} queryID Query ID
271 SavedQueriesModel.prototype.removeQuery = function ( queryID ) {
272 const query = this.getItemByID( queryID );
275 // Check if this item was the default
276 if ( String( this.getDefault() ) === String( queryID ) ) {
277 // Nulify the default
278 this.setDefault( null );
281 this.removeItems( [ query ] );
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
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;
301 return this.getItems().filter( ( item ) => OO.compare(
302 item.getCombinedData(),
308 * Get query by its identifier
311 * @param {string} queryID Query identifier
312 * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching
313 * the search. Undefined if not found.
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.
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
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
348 SavedQueriesModel.prototype.buildParamsFromData = function ( data ) {
350 // Return parameter representation
351 return this.filtersModel.getMinimizedParamRepresentation( $.extend( true, {},
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
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();
370 if ( this.getDefault() ) {
371 obj.default = this.getDefault();
378 * Set a default query. Null to unset default.
380 * @param {string} itemID Query identifier
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 );
392 this.emit( 'default', itemID );
397 * Get the default query ID
399 * @return {string} Default query identifier
401 SavedQueriesModel.prototype.getDefault = function () {
406 * Check if the saved queries were converted
408 * @return {boolean} Saved queries were converted from the previous
409 * version to the new version
411 SavedQueriesModel.prototype.isConverted = function () {
412 return this.converted;
415 module.exports = SavedQueriesModel;