Merge "Improve "selfmove" message's wording"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.SavedQueriesModel.js
1 ( function ( mw, $ ) {
2 /**
3 * View model for saved queries
4 *
5 * @class
6 * @mixins OO.EventEmitter
7 * @mixins OO.EmitterList
8 *
9 * @constructor
10 * @param {Object} [config] Configuration options
11 * @cfg {string} [default] Default query ID
12 */
13 mw.rcfilters.dm.SavedQueriesModel = function MwRcfiltersDmSavedQueriesModel( config ) {
14 config = config || {};
15
16 // Mixin constructor
17 OO.EventEmitter.call( this );
18 OO.EmitterList.call( this );
19
20 this.default = config.default;
21 this.baseState = {};
22
23 // Events
24 this.aggregate( { update: 'itemUpdate' } );
25 };
26
27 /* Initialization */
28
29 OO.initClass( mw.rcfilters.dm.SavedQueriesModel );
30 OO.mixinClass( mw.rcfilters.dm.SavedQueriesModel, OO.EventEmitter );
31 OO.mixinClass( mw.rcfilters.dm.SavedQueriesModel, OO.EmitterList );
32
33 /* Events */
34
35 /**
36 * @event initialize
37 *
38 * Model is initialized
39 */
40
41 /**
42 * @event itemUpdate
43 * @param {mw.rcfilters.dm.SavedQueryItemModel} Changed item
44 *
45 * An item has changed
46 */
47
48 /**
49 * @event default
50 * @param {string} New default ID
51 *
52 * The default has changed
53 */
54
55 /* Methods */
56
57 /**
58 * Initialize the saved queries model by reading it from the user's settings.
59 * The structure of the saved queries is:
60 * {
61 * default: (string) Query ID
62 * queries:{
63 * query_id_1: {
64 * data:{
65 * filters: (Object) Minimal definition of the filters
66 * highlights: (Object) Definition of the highlights
67 * },
68 * label: (optional) Name of this query
69 * }
70 * }
71 * }
72 *
73 * @param {Object} [savedQueries] An object with the saved queries with
74 * the above structure.
75 * @param {Object} [baseState] An object representing the base state
76 * so we can normalize the data
77 * @param {string[]} [ignoreFilters] Filters to ignore and remove from
78 * the data
79 * @fires initialize
80 */
81 mw.rcfilters.dm.SavedQueriesModel.prototype.initialize = function ( savedQueries, baseState, ignoreFilters ) {
82 var items = [],
83 defaultItem = null;
84
85 savedQueries = savedQueries || {};
86 ignoreFilters = ignoreFilters || {};
87
88 this.baseState = baseState;
89
90 this.clearItems();
91 $.each( savedQueries.queries || {}, function ( id, obj ) {
92 var item,
93 normalizedData = $.extend( true, {}, baseState, obj.data ),
94 isDefault = String( savedQueries.default ) === String( id );
95
96 // Backwards-compat fix: We stored the 'highlight' state with
97 // "1" and "0" instead of true/false; for already-stored states,
98 // we need to fix that.
99 // NOTE: Since this feature is only available in beta, we should
100 // not need this line when we release this to the general wikis.
101 // This method will automatically fix all saved queries anyways
102 // for existing users, who are only betalabs users at the moment.
103 normalizedData.highlights.highlight = !!Number( normalizedData.highlights.highlight );
104
105 // Backwards-compat fix: Remove sticky parameters from the 'ignoreFilters' list
106 ignoreFilters.forEach( function ( name ) {
107 delete normalizedData.filters[ name ];
108 } );
109
110 item = new mw.rcfilters.dm.SavedQueryItemModel(
111 id,
112 obj.label,
113 normalizedData,
114 { 'default': isDefault }
115 );
116
117 if ( isDefault ) {
118 defaultItem = item;
119 }
120
121 items.push( item );
122 } );
123
124 if ( defaultItem ) {
125 this.default = defaultItem.getID();
126 }
127
128 this.addItems( items );
129
130 this.emit( 'initialize' );
131 };
132
133 /**
134 * Add a query item
135 *
136 * @param {string} label Label for the new query
137 * @param {Object} data Data for the new query
138 * @return {string} ID of the newly added query
139 */
140 mw.rcfilters.dm.SavedQueriesModel.prototype.addNewQuery = function ( label, data ) {
141 var randomID = ( new Date() ).getTime(),
142 normalizedData = $.extend( true, {}, this.baseState, data );
143
144 // Add item
145 this.addItems( [
146 new mw.rcfilters.dm.SavedQueryItemModel(
147 randomID,
148 label,
149 normalizedData
150 )
151 ] );
152
153 return randomID;
154 };
155
156 /**
157 * Remove query from model
158 *
159 * @param {string} queryID Query ID
160 */
161 mw.rcfilters.dm.SavedQueriesModel.prototype.removeQuery = function ( queryID ) {
162 var query = this.getItemByID( queryID );
163
164 if ( query ) {
165 // Check if this item was the default
166 if ( String( this.getDefault() ) === String( queryID ) ) {
167 // Nulify the default
168 this.setDefault( null );
169 }
170
171 this.removeItems( [ query ] );
172 }
173 };
174
175 /**
176 * Get an item that matches the requested query
177 *
178 * @param {Object} fullQueryComparison Object representing all filters and highlights to compare
179 * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model
180 */
181 mw.rcfilters.dm.SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) {
182 var model = this;
183
184 fullQueryComparison = this.getDifferenceFromBase( fullQueryComparison );
185
186 return this.getItems().filter( function ( item ) {
187 var comparedData = model.getDifferenceFromBase( item.getData() );
188 return OO.compare(
189 comparedData,
190 fullQueryComparison
191 );
192 } )[ 0 ];
193 };
194
195 /**
196 * Get a minimal representation of the state for comparison
197 *
198 * @param {Object} state Given state
199 * @return {Object} Minimal state
200 */
201 mw.rcfilters.dm.SavedQueriesModel.prototype.getDifferenceFromBase = function ( state ) {
202 var result = { filters: {}, highlights: {}, invert: state.invert },
203 baseState = this.baseState;
204
205 // XOR results
206 $.each( state.filters, function ( name, value ) {
207 if ( baseState.filters !== undefined && baseState.filters[ name ] !== value ) {
208 result.filters[ name ] = value;
209 }
210 } );
211
212 $.each( state.highlights, function ( name, value ) {
213 if ( baseState.highlights !== undefined && baseState.highlights[ name ] !== value && name !== 'highlight' ) {
214 result.highlights[ name ] = value;
215 }
216 } );
217
218 return result;
219 };
220 /**
221 * Get query by its identifier
222 *
223 * @param {string} queryID Query identifier
224 * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching
225 * the search. Undefined if not found.
226 */
227 mw.rcfilters.dm.SavedQueriesModel.prototype.getItemByID = function ( queryID ) {
228 return this.getItems().filter( function ( item ) {
229 return item.getID() === queryID;
230 } )[ 0 ];
231 };
232
233 /**
234 * Get the object representing the state of the entire model and items
235 *
236 * @return {Object} Object representing the state of the model and items
237 */
238 mw.rcfilters.dm.SavedQueriesModel.prototype.getState = function () {
239 var obj = { queries: {} };
240
241 // Translate the items to the saved object
242 this.getItems().forEach( function ( item ) {
243 var itemState = item.getState();
244
245 obj.queries[ item.getID() ] = itemState;
246 } );
247
248 if ( this.getDefault() ) {
249 obj.default = this.getDefault();
250 }
251
252 return obj;
253 };
254
255 /**
256 * Set a default query. Null to unset default.
257 *
258 * @param {string} itemID Query identifier
259 * @fires default
260 */
261 mw.rcfilters.dm.SavedQueriesModel.prototype.setDefault = function ( itemID ) {
262 if ( this.default !== itemID ) {
263 this.default = itemID;
264
265 // Set for individual itens
266 this.getItems().forEach( function ( item ) {
267 item.toggleDefault( item.getID() === itemID );
268 } );
269
270 this.emit( 'default', itemID );
271 }
272 };
273
274 /**
275 * Get the default query ID
276 *
277 * @return {string} Default query identifier
278 */
279 mw.rcfilters.dm.SavedQueriesModel.prototype.getDefault = function () {
280 return this.default;
281 };
282 }( mediaWiki, jQuery ) );