resources: Strip '$' and 'mw' from file closures
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.UriProcessor.js
1 ( function () {
2 /* eslint no-underscore-dangle: "off" */
3 /**
4 * URI Processor for RCFilters
5 *
6 * @class
7 *
8 * @constructor
9 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
10 * @param {Object} [config] Configuration object
11 * @cfg {boolean} [normalizeTarget] Dictates whether or not to go through the
12 * title normalization to separate title subpage/parts into the target= url
13 * parameter
14 */
15 mw.rcfilters.UriProcessor = function MwRcfiltersController( filtersModel, config ) {
16 config = config || {};
17 this.filtersModel = filtersModel;
18
19 this.normalizeTarget = !!config.normalizeTarget;
20 };
21
22 /* Initialization */
23 OO.initClass( mw.rcfilters.UriProcessor );
24
25 /* Static methods */
26
27 /**
28 * Replace the url history through replaceState
29 *
30 * @param {mw.Uri} newUri New URI to replace
31 */
32 mw.rcfilters.UriProcessor.static.replaceState = function ( newUri ) {
33 window.history.replaceState(
34 { tag: 'rcfilters' },
35 document.title,
36 newUri.toString()
37 );
38 };
39
40 /**
41 * Push the url to history through pushState
42 *
43 * @param {mw.Uri} newUri New URI to push
44 */
45 mw.rcfilters.UriProcessor.static.pushState = function ( newUri ) {
46 window.history.pushState(
47 { tag: 'rcfilters' },
48 document.title,
49 newUri.toString()
50 );
51 };
52
53 /* Methods */
54
55 /**
56 * Get the version that this URL query is tagged with.
57 *
58 * @param {Object} [uriQuery] URI query
59 * @return {number} URL version
60 */
61 mw.rcfilters.UriProcessor.prototype.getVersion = function ( uriQuery ) {
62 uriQuery = uriQuery || new mw.Uri().query;
63
64 return Number( uriQuery.urlversion || 1 );
65 };
66
67 /**
68 * Get an updated mw.Uri object based on the model state
69 *
70 * @param {mw.Uri} [uri] An external URI to build the new uri
71 * with. This is mainly for tests, to be able to supply external query
72 * parameters and make sure they are retained.
73 * @return {mw.Uri} Updated Uri
74 */
75 mw.rcfilters.UriProcessor.prototype.getUpdatedUri = function ( uri ) {
76 var normalizedUri = this._normalizeTargetInUri( uri || new mw.Uri() ),
77 unrecognizedParams = this.getUnrecognizedParams( normalizedUri.query );
78
79 normalizedUri.query = this.filtersModel.getMinimizedParamRepresentation(
80 $.extend(
81 true,
82 {},
83 normalizedUri.query,
84 // The representation must be expanded so it can
85 // override the uri query params but we then output
86 // a minimized version for the entire URI representation
87 // for the method
88 this.filtersModel.getExpandedParamRepresentation()
89 )
90 );
91
92 // Reapply unrecognized params and url version
93 normalizedUri.query = $.extend(
94 true,
95 {},
96 normalizedUri.query,
97 unrecognizedParams,
98 { urlversion: '2' }
99 );
100
101 return normalizedUri;
102 };
103
104 /**
105 * Move the subpage to the target parameter
106 *
107 * @param {mw.Uri} uri
108 * @return {mw.Uri}
109 * @private
110 */
111 mw.rcfilters.UriProcessor.prototype._normalizeTargetInUri = function ( uri ) {
112 var parts,
113 // matches [/wiki/]SpecialNS:RCL/[Namespace:]Title/Subpage/Subsubpage/etc
114 re = /^((?:\/.+?\/)?.*?:.*?)\/(.*)$/;
115
116 if ( !this.normalizeTarget ) {
117 return uri;
118 }
119
120 // target in title param
121 if ( uri.query.title ) {
122 parts = uri.query.title.match( re );
123 if ( parts ) {
124 uri.query.title = parts[ 1 ];
125 uri.query.target = parts[ 2 ];
126 }
127 }
128
129 // target in path
130 parts = mw.Uri.decode( uri.path ).match( re );
131 if ( parts ) {
132 uri.path = parts[ 1 ];
133 uri.query.target = parts[ 2 ];
134 }
135
136 return uri;
137 };
138
139 /**
140 * Get an object representing given parameters that are unrecognized by the model
141 *
142 * @param {Object} params Full params object
143 * @return {Object} Unrecognized params
144 */
145 mw.rcfilters.UriProcessor.prototype.getUnrecognizedParams = function ( params ) {
146 // Start with full representation
147 var givenParamNames = Object.keys( params ),
148 unrecognizedParams = $.extend( true, {}, params );
149
150 // Extract unrecognized parameters
151 Object.keys( this.filtersModel.getEmptyParameterState() ).forEach( function ( paramName ) {
152 // Remove recognized params
153 if ( givenParamNames.indexOf( paramName ) > -1 ) {
154 delete unrecognizedParams[ paramName ];
155 }
156 } );
157
158 return unrecognizedParams;
159 };
160
161 /**
162 * Update the URL of the page to reflect current filters
163 *
164 * This should not be called directly from outside the controller.
165 * If an action requires changing the URL, it should either use the
166 * highlighting actions below, or call #updateChangesList which does
167 * the uri corrections already.
168 *
169 * @param {Object} [params] Extra parameters to add to the API call
170 */
171 mw.rcfilters.UriProcessor.prototype.updateURL = function ( params ) {
172 var currentUri = new mw.Uri(),
173 updatedUri = this.getUpdatedUri();
174
175 updatedUri.extend( params || {} );
176
177 if (
178 this.getVersion( currentUri.query ) !== 2 ||
179 this.isNewState( currentUri.query, updatedUri.query )
180 ) {
181 this.constructor.static.replaceState( updatedUri );
182 }
183 };
184
185 /**
186 * Update the filters model based on the URI query
187 * This happens on initialization, and from this moment on,
188 * we consider the system synchronized, and the model serves
189 * as the source of truth for the URL.
190 *
191 * This methods should only be called once on initialization.
192 * After initialization, the model updates the URL, not the
193 * other way around.
194 *
195 * @param {Object} [uriQuery] URI query
196 */
197 mw.rcfilters.UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
198 uriQuery = uriQuery || this._normalizeTargetInUri( new mw.Uri() ).query;
199 this.filtersModel.updateStateFromParams(
200 this._getNormalizedQueryParams( uriQuery )
201 );
202 };
203
204 /**
205 * Compare two URI queries to decide whether they are different
206 * enough to represent a new state.
207 *
208 * @param {Object} currentUriQuery Current Uri query
209 * @param {Object} updatedUriQuery Updated Uri query
210 * @return {boolean} This is a new state
211 */
212 mw.rcfilters.UriProcessor.prototype.isNewState = function ( currentUriQuery, updatedUriQuery ) {
213 var currentParamState, updatedParamState,
214 notEquivalent = function ( obj1, obj2 ) {
215 var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
216 return keys.some( function ( key ) {
217 return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
218 } );
219 };
220
221 // Compare states instead of parameters
222 // This will allow us to always have a proper check of whether
223 // the requested new url is one to change or not, regardless of
224 // actual parameter visibility/representation in the URL
225 currentParamState = $.extend(
226 true,
227 {},
228 this.filtersModel.getMinimizedParamRepresentation( currentUriQuery ),
229 this.getUnrecognizedParams( currentUriQuery )
230 );
231 updatedParamState = $.extend(
232 true,
233 {},
234 this.filtersModel.getMinimizedParamRepresentation( updatedUriQuery ),
235 this.getUnrecognizedParams( updatedUriQuery )
236 );
237
238 return notEquivalent( currentParamState, updatedParamState );
239 };
240
241 /**
242 * Check whether the given query has parameters that are
243 * recognized as parameters we should load the system with
244 *
245 * @param {mw.Uri} [uriQuery] Given URI query
246 * @return {boolean} Query contains valid recognized parameters
247 */
248 mw.rcfilters.UriProcessor.prototype.doesQueryContainRecognizedParams = function ( uriQuery ) {
249 var anyValidInUrl,
250 validParameterNames = Object.keys( this.filtersModel.getEmptyParameterState() );
251
252 uriQuery = uriQuery || new mw.Uri().query;
253
254 anyValidInUrl = Object.keys( uriQuery ).some( function ( parameter ) {
255 return validParameterNames.indexOf( parameter ) > -1;
256 } );
257
258 // URL version 2 is allowed to be empty or within nonrecognized params
259 return anyValidInUrl || this.getVersion( uriQuery ) === 2;
260 };
261
262 /**
263 * Get the adjusted URI params based on the url version
264 * If the urlversion is not 2, the parameters are merged with
265 * the model's defaults.
266 * Always merge in the hidden parameter defaults.
267 *
268 * @private
269 * @param {Object} uriQuery Current URI query
270 * @return {Object} Normalized parameters
271 */
272 mw.rcfilters.UriProcessor.prototype._getNormalizedQueryParams = function ( uriQuery ) {
273 // Check whether we are dealing with urlversion=2
274 // If we are, we do not merge the initial request with
275 // defaults. Not having urlversion=2 means we need to
276 // reproduce the server-side request and merge the
277 // requested parameters (or starting state) with the
278 // wiki default.
279 // Any subsequent change of the URL through the RCFilters
280 // system will receive 'urlversion=2'
281 var base = this.getVersion( uriQuery ) === 2 ?
282 {} :
283 this.filtersModel.getDefaultParams();
284
285 return $.extend(
286 true,
287 {},
288 this.filtersModel.getMinimizedParamRepresentation(
289 $.extend( true, {}, base, uriQuery )
290 ),
291 { urlversion: '2' }
292 );
293 };
294 }() );