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