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