1 /* eslint no-underscore-dangle: "off" */
3 * URI Processor for RCFilters
5 * @class mw.rcfilters.UriProcessor
8 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
9 * @param {Object} [config] Configuration object
10 * @cfg {boolean} [normalizeTarget] Dictates whether or not to go through the
11 * title normalization to separate title subpage/parts into the target= url
14 var 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 var 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
) {
112 // matches [/wiki/]SpecialNS:RCL/[Namespace:]Title/Subpage/Subsubpage/etc
113 re
= /^((?:\/.+?\/)?.*?:.*?)\/(.*)$/;
115 if ( !this.normalizeTarget
) {
119 // target in title param
120 if ( uri
.query
.title
) {
121 parts
= uri
.query
.title
.match( re
);
123 uri
.query
.title
= parts
[ 1 ];
124 uri
.query
.target
= parts
[ 2 ];
129 parts
= mw
.Uri
.decode( uri
.path
).match( re
);
131 uri
.path
= parts
[ 1 ];
132 uri
.query
.target
= parts
[ 2 ];
139 * Get an object representing given parameters that are unrecognized by the model
141 * @param {Object} params Full params object
142 * @return {Object} Unrecognized params
144 UriProcessor
.prototype.getUnrecognizedParams = function ( params
) {
145 // Start with full representation
146 var givenParamNames
= Object
.keys( params
),
147 unrecognizedParams
= $.extend( true, {}, params
);
149 // Extract unrecognized parameters
150 Object
.keys( this.filtersModel
.getEmptyParameterState() ).forEach( function ( paramName
) {
151 // Remove recognized params
152 if ( givenParamNames
.indexOf( paramName
) > -1 ) {
153 delete unrecognizedParams
[ paramName
];
157 return unrecognizedParams
;
161 * Update the URL of the page to reflect current filters
163 * This should not be called directly from outside the controller.
164 * If an action requires changing the URL, it should either use the
165 * highlighting actions below, or call #updateChangesList which does
166 * the uri corrections already.
168 * @param {Object} [params] Extra parameters to add to the API call
170 UriProcessor
.prototype.updateURL = function ( params
) {
171 var currentUri
= new mw
.Uri(),
172 updatedUri
= this.getUpdatedUri();
174 updatedUri
.extend( params
|| {} );
177 this.getVersion( currentUri
.query
) !== 2 ||
178 this.isNewState( currentUri
.query
, updatedUri
.query
)
180 this.constructor.static.replaceState( updatedUri
);
185 * Update the filters model based on the URI query
186 * This happens on initialization, and from this moment on,
187 * we consider the system synchronized, and the model serves
188 * as the source of truth for the URL.
190 * This methods should only be called once on initialization.
191 * After initialization, the model updates the URL, not the
194 * @param {Object} [uriQuery] URI query
196 UriProcessor
.prototype.updateModelBasedOnQuery = function ( uriQuery
) {
197 uriQuery
= uriQuery
|| this._normalizeTargetInUri( new mw
.Uri() ).query
;
198 this.filtersModel
.updateStateFromParams(
199 this._getNormalizedQueryParams( uriQuery
)
204 * Compare two URI queries to decide whether they are different
205 * enough to represent a new state.
207 * @param {Object} currentUriQuery Current Uri query
208 * @param {Object} updatedUriQuery Updated Uri query
209 * @return {boolean} This is a new state
211 UriProcessor
.prototype.isNewState = function ( currentUriQuery
, updatedUriQuery
) {
212 var currentParamState
, updatedParamState
,
213 notEquivalent = function ( obj1
, obj2
) {
214 var keys
= Object
.keys( obj1
).concat( Object
.keys( obj2
) );
215 return keys
.some( function ( key
) {
216 return obj1
[ key
] != obj2
[ key
]; // eslint-disable-line eqeqeq
220 // Compare states instead of parameters
221 // This will allow us to always have a proper check of whether
222 // the requested new url is one to change or not, regardless of
223 // actual parameter visibility/representation in the URL
224 currentParamState
= $.extend(
227 this.filtersModel
.getMinimizedParamRepresentation( currentUriQuery
),
228 this.getUnrecognizedParams( currentUriQuery
)
230 updatedParamState
= $.extend(
233 this.filtersModel
.getMinimizedParamRepresentation( updatedUriQuery
),
234 this.getUnrecognizedParams( updatedUriQuery
)
237 return notEquivalent( currentParamState
, updatedParamState
);
241 * Check whether the given query has parameters that are
242 * recognized as parameters we should load the system with
244 * @param {mw.Uri} [uriQuery] Given URI query
245 * @return {boolean} Query contains valid recognized parameters
247 UriProcessor
.prototype.doesQueryContainRecognizedParams = function ( uriQuery
) {
249 validParameterNames
= Object
.keys( this.filtersModel
.getEmptyParameterState() );
251 uriQuery
= uriQuery
|| new mw
.Uri().query
;
253 anyValidInUrl
= Object
.keys( uriQuery
).some( function ( parameter
) {
254 return validParameterNames
.indexOf( parameter
) > -1;
257 // URL version 2 is allowed to be empty or within nonrecognized params
258 return anyValidInUrl
|| this.getVersion( uriQuery
) === 2;
262 * Get the adjusted URI params based on the url version
263 * If the urlversion is not 2, the parameters are merged with
264 * the model's defaults.
265 * Always merge in the hidden parameter defaults.
268 * @param {Object} uriQuery Current URI query
269 * @return {Object} Normalized parameters
271 UriProcessor
.prototype._getNormalizedQueryParams = function ( uriQuery
) {
272 // Check whether we are dealing with urlversion=2
273 // If we are, we do not merge the initial request with
274 // defaults. Not having urlversion=2 means we need to
275 // reproduce the server-side request and merge the
276 // requested parameters (or starting state) with the
278 // Any subsequent change of the URL through the RCFilters
279 // system will receive 'urlversion=2'
280 var base
= this.getVersion( uriQuery
) === 2 ?
282 this.filtersModel
.getDefaultParams();
287 this.filtersModel
.getMinimizedParamRepresentation(
288 $.extend( true, {}, base
, uriQuery
)
294 module
.exports
= UriProcessor
;