1 /* eslint no-underscore-dangle: "off" */
3 * URI Processor for RCFilters.
6 * @memberof mw.rcfilters
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
14 const UriProcessor = function MwRcfiltersController( filtersModel, config ) {
15 config = config || {};
16 this.filtersModel = filtersModel;
18 this.normalizeTarget = !!config.normalizeTarget;
22 OO.initClass( UriProcessor );
27 * Replace the url history through replaceState
29 * @param {mw.Uri} newUri New URI to replace
31 UriProcessor.static.replaceState = function ( newUri ) {
32 window.history.replaceState(
40 * Push the url to history through pushState
42 * @param {mw.Uri} newUri New URI to push
44 UriProcessor.static.pushState = function ( newUri ) {
45 window.history.pushState(
55 * Get the version that this URL query is tagged with.
57 * @param {Object} [uriQuery] URI query
58 * @return {number} URL version
60 UriProcessor.prototype.getVersion = function ( uriQuery ) {
61 uriQuery = uriQuery || new mw.Uri().query;
63 return Number( uriQuery.urlversion || 1 );
67 * Get an updated mw.Uri object based on the model state
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
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(
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
87 this.filtersModel.getExpandedParamRepresentation()
91 // Reapply unrecognized params and url version
92 normalizedUri.query = $.extend(
100 return normalizedUri;
104 * Move the subpage to the target parameter
106 * @param {mw.Uri} uri
110 UriProcessor.prototype._normalizeTargetInUri = function ( uri ) {
111 // matches [/wiki/]SpecialNS:RCL/[Namespace:]Title/Subpage/Subsubpage/etc
112 const re = /^((?:\/.+?\/)?.*?:.*?)\/(.*)$/;
114 if ( !this.normalizeTarget ) {
118 // target in title param
119 if ( uri.query.title ) {
120 const titleParts = uri.query.title.match( re );
122 uri.query.title = titleParts[ 1 ];
123 uri.query.target = titleParts[ 2 ];
128 const pathParts = mw.Uri.decode( uri.path ).match( re );
130 uri.path = pathParts[ 1 ];
131 uri.query.target = pathParts[ 2 ];
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
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 ];
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
169 UriProcessor.prototype.updateURL = function ( params ) {
170 const currentUri = new mw.Uri(),
171 updatedUri = this.getUpdatedUri();
173 updatedUri.extend( params || {} );
176 this.getVersion( currentUri.query ) !== 2 ||
177 this.isNewState( currentUri.query, updatedUri.query )
179 this.constructor.static.replaceState( updatedUri );
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
193 * @param {Object} [uriQuery] URI query
195 UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
196 uriQuery = uriQuery || this._normalizeTargetInUri( new mw.Uri() ).query;
197 this.filtersModel.updateStateFromParams(
198 this._getNormalizedQueryParams( uriQuery )
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
210 UriProcessor.prototype.isNewState = function ( currentUriQuery, updatedUriQuery ) {
211 const notEquivalent = function ( obj1, obj2 ) {
212 const keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
214 ( key ) => obj1[ key ] != obj2[ key ] // eslint-disable-line eqeqeq
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(
225 this.filtersModel.getMinimizedParamRepresentation( currentUriQuery ),
226 this.getUnrecognizedParams( currentUriQuery )
228 const updatedParamState = $.extend(
231 this.filtersModel.getMinimizedParamRepresentation( updatedUriQuery ),
232 this.getUnrecognizedParams( updatedUriQuery )
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.
245 * @param {Object} uriQuery Current URI query
246 * @return {Object} Normalized parameters
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
255 // Any subsequent change of the URL through the RCFilters
256 // system will receive 'urlversion=2'
257 const base = this.getVersion( uriQuery ) === 2 ?
259 this.filtersModel.getDefaultParams();
264 this.filtersModel.getMinimizedParamRepresentation(
265 $.extend( true, {}, base, uriQuery )
271 module.exports = UriProcessor;