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