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