Merge "ESLint ecmaVersion setting is not needed if env is es6"
[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 {Object} [uriQuery] An external URI query to build the new uri
61 * with. This is mainly for tests, to be able to supply external parameters
62 * and make sure they are retained.
63 * @return {mw.Uri} Updated Uri
64 */
65 mw.rcfilters.UriProcessor.prototype.getUpdatedUri = function ( uriQuery ) {
66 var titlePieces,
67 uri = new mw.Uri(),
68 unrecognizedParams = this.getUnrecognizedParams( uriQuery || uri.query );
69
70 if ( uriQuery ) {
71 // This is mainly for tests, to be able to give the method
72 // an initial URI Query and test that it retains parameters
73 uri.query = uriQuery;
74 }
75
76 // Normalize subpage to use &target= so we are always
77 // consistent in Special:RecentChangesLinked between the
78 // ?title=Special:RecentChangesLinked/TargetPage and
79 // ?title=Special:RecentChangesLinked&target=TargetPage
80 if ( uri.query.title && uri.query.title.indexOf( '/' ) !== -1 ) {
81 titlePieces = uri.query.title.split( '/' );
82
83 unrecognizedParams.title = titlePieces.shift();
84 unrecognizedParams.target = titlePieces.join( '/' );
85 }
86
87 uri.query = this.filtersModel.getMinimizedParamRepresentation(
88 $.extend(
89 true,
90 {},
91 uri.query,
92 // The representation must be expanded so it can
93 // override the uri query params but we then output
94 // a minimized version for the entire URI representation
95 // for the method
96 this.filtersModel.getExpandedParamRepresentation()
97 )
98 );
99
100 // Reapply unrecognized params and url version
101 uri.query = $.extend( true, {}, uri.query, unrecognizedParams, { urlversion: '2' } );
102 return uri;
103 };
104
105 /**
106 * Get an object representing given parameters that are unrecognized by the model
107 *
108 * @param {Object} params Full params object
109 * @return {Object} Unrecognized params
110 */
111 mw.rcfilters.UriProcessor.prototype.getUnrecognizedParams = function ( params ) {
112 // Start with full representation
113 var givenParamNames = Object.keys( params ),
114 unrecognizedParams = $.extend( true, {}, params );
115
116 // Extract unrecognized parameters
117 Object.keys( this.filtersModel.getEmptyParameterState() ).forEach( function ( paramName ) {
118 // Remove recognized params
119 if ( givenParamNames.indexOf( paramName ) > -1 ) {
120 delete unrecognizedParams[ paramName ];
121 }
122 } );
123
124 return unrecognizedParams;
125 };
126
127 /**
128 * Update the URL of the page to reflect current filters
129 *
130 * This should not be called directly from outside the controller.
131 * If an action requires changing the URL, it should either use the
132 * highlighting actions below, or call #updateChangesList which does
133 * the uri corrections already.
134 *
135 * @param {Object} [params] Extra parameters to add to the API call
136 */
137 mw.rcfilters.UriProcessor.prototype.updateURL = function ( params ) {
138 var currentUri = new mw.Uri(),
139 updatedUri = this.getUpdatedUri();
140
141 updatedUri.extend( params || {} );
142
143 if (
144 this.getVersion( currentUri.query ) !== 2 ||
145 this.isNewState( currentUri.query, updatedUri.query )
146 ) {
147 this.constructor.static.replaceState( updatedUri );
148 }
149 };
150
151 /**
152 * Update the filters model based on the URI query
153 * This happens on initialization, and from this moment on,
154 * we consider the system synchronized, and the model serves
155 * as the source of truth for the URL.
156 *
157 * This methods should only be called once on initialiation.
158 * After initialization, the model updates the URL, not the
159 * other way around.
160 *
161 * @param {Object} [uriQuery] URI query
162 */
163 mw.rcfilters.UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
164 this.filtersModel.updateStateFromParams(
165 this._getNormalizedQueryParams( uriQuery || new mw.Uri().query )
166 );
167 };
168
169 /**
170 * Compare two URI queries to decide whether they are different
171 * enough to represent a new state.
172 *
173 * @param {Object} currentUriQuery Current Uri query
174 * @param {Object} updatedUriQuery Updated Uri query
175 * @return {boolean} This is a new state
176 */
177 mw.rcfilters.UriProcessor.prototype.isNewState = function ( currentUriQuery, updatedUriQuery ) {
178 var currentParamState, updatedParamState,
179 notEquivalent = function ( obj1, obj2 ) {
180 var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
181 return keys.some( function ( key ) {
182 return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
183 } );
184 };
185
186 // Compare states instead of parameters
187 // This will allow us to always have a proper check of whether
188 // the requested new url is one to change or not, regardless of
189 // actual parameter visibility/representation in the URL
190 currentParamState = $.extend(
191 true,
192 {},
193 this.filtersModel.getMinimizedParamRepresentation( currentUriQuery ),
194 this.getUnrecognizedParams( currentUriQuery )
195 );
196 updatedParamState = $.extend(
197 true,
198 {},
199 this.filtersModel.getMinimizedParamRepresentation( updatedUriQuery ),
200 this.getUnrecognizedParams( updatedUriQuery )
201 );
202
203 return notEquivalent( currentParamState, updatedParamState );
204 };
205
206 /**
207 * Check whether the given query has parameters that are
208 * recognized as parameters we should load the system with
209 *
210 * @param {mw.Uri} [uriQuery] Given URI query
211 * @return {boolean} Query contains valid recognized parameters
212 */
213 mw.rcfilters.UriProcessor.prototype.doesQueryContainRecognizedParams = function ( uriQuery ) {
214 var anyValidInUrl,
215 validParameterNames = Object.keys( this.filtersModel.getEmptyParameterState() );
216
217 uriQuery = uriQuery || new mw.Uri().query;
218
219 anyValidInUrl = Object.keys( uriQuery ).some( function ( parameter ) {
220 return validParameterNames.indexOf( parameter ) > -1;
221 } );
222
223 // URL version 2 is allowed to be empty or within nonrecognized params
224 return anyValidInUrl || this.getVersion( uriQuery ) === 2;
225 };
226
227 /**
228 * Get the adjusted URI params based on the url version
229 * If the urlversion is not 2, the parameters are merged with
230 * the model's defaults.
231 * Always merge in the hidden parameter defaults.
232 *
233 * @private
234 * @param {Object} uriQuery Current URI query
235 * @return {Object} Normalized parameters
236 */
237 mw.rcfilters.UriProcessor.prototype._getNormalizedQueryParams = function ( uriQuery ) {
238 // Check whether we are dealing with urlversion=2
239 // If we are, we do not merge the initial request with
240 // defaults. Not having urlversion=2 means we need to
241 // reproduce the server-side request and merge the
242 // requested parameters (or starting state) with the
243 // wiki default.
244 // Any subsequent change of the URL through the RCFilters
245 // system will receive 'urlversion=2'
246 var hiddenParamDefaults = this.filtersModel.getDefaultHiddenParams(),
247 base = this.getVersion( uriQuery ) === 2 ?
248 {} :
249 this.filtersModel.getDefaultParams();
250
251 return $.extend(
252 true,
253 {},
254 this.filtersModel.getMinimizedParamRepresentation(
255 $.extend( true, {}, hiddenParamDefaults, base, uriQuery )
256 ),
257 { urlversion: '2' }
258 );
259 };
260 }( mediaWiki, jQuery ) );