RCFilters: Only normalize title with 'target' when it is needed
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.UriProcessor.js
index ba61ba9..b8ff22e 100644 (file)
@@ -3,14 +3,20 @@
        /**
         * URI Processor for RCFilters
         *
+        * @class
+        *
+        * @constructor
         * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
+        * @param {Object} [config] Configuration object
+        * @cfg {boolean} [normalizeTarget] Dictates whether or not to go through the
+        *  title normalization to separate title subpage/parts into the target= url
+        *  parameter
         */
-       mw.rcfilters.UriProcessor = function MwRcfiltersController( filtersModel ) {
-               this.emptyParameterState = {};
+       mw.rcfilters.UriProcessor = function MwRcfiltersController( filtersModel, config ) {
+               config = config || {};
                this.filtersModel = filtersModel;
 
-               // Initialize
-               this._buildEmptyParameterState();
+               this.normalizeTarget = !!config.normalizeTarget;
        };
 
        /* Initialization */
        };
 
        /**
-        * Update the filters model based on the URI query
-        * This happens on initialization, and from this moment on,
-        * we consider the system synchronized, and the model serves
-        * as the source of truth for the URL.
+        * Get an updated mw.Uri object based on the model state
         *
-        * This methods should only be called once on initialiation.
-        * After initialization, the model updates the URL, not the
-        * other way around.
-        *
-        * @param {Object} [uriQuery] URI query
+        * @param {mw.Uri} [uri] An external URI to build the new uri
+        *  with. This is mainly for tests, to be able to supply external query
+        *  parameters and make sure they are retained.
+        * @return {mw.Uri} Updated Uri
         */
-       mw.rcfilters.UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
-               var parameters = this._getNormalizedQueryParams( uriQuery || new mw.Uri().query );
-
-               // Update filter states
-               this.filtersModel.toggleFiltersSelected(
-                       this.filtersModel.getFiltersFromParameters(
-                               parameters
+       mw.rcfilters.UriProcessor.prototype.getUpdatedUri = function ( uri ) {
+               var normalizedUri = this._normalizeTargetInUri( uri || new mw.Uri() ),
+                       unrecognizedParams = this.getUnrecognizedParams( normalizedUri.query );
+
+               normalizedUri.query = this.filtersModel.getMinimizedParamRepresentation(
+                       $.extend(
+                               true,
+                               {},
+                               normalizedUri.query,
+                               // The representation must be expanded so it can
+                               // override the uri query params but we then output
+                               // a minimized version for the entire URI representation
+                               // for the method
+                               this.filtersModel.getExpandedParamRepresentation()
                        )
                );
 
-               this.filtersModel.toggleInvertedNamespaces( !!Number( parameters.invert ) );
+               // Reapply unrecognized params and url version
+               normalizedUri.query = $.extend(
+                       true,
+                       {},
+                       normalizedUri.query,
+                       unrecognizedParams,
+                       { urlversion: '2' }
+               );
+
+               return normalizedUri;
+       };
 
-               // Update highlight state
-               this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
-               this.filtersModel.getItems().forEach( function ( filterItem ) {
-                       var color = parameters[ filterItem.getName() + '_color' ];
-                       if ( color ) {
-                               filterItem.setHighlightColor( color );
-                       } else {
-                               filterItem.clearHighlightColor();
+       /**
+        * Move the subpage to the target parameter
+        *
+        * @param {mw.Uri} uri
+        * @return {mw.Uri}
+        * @private
+        */
+       mw.rcfilters.UriProcessor.prototype._normalizeTargetInUri = function ( uri ) {
+               var parts,
+                       // matches [/wiki/]SpecialNS:RCL/[Namespace:]Title/Subpage/Subsubpage/etc
+                       re = /^((?:\/.+?\/)?.*?:.*?)\/(.*)$/;
+
+               if ( !this.normalizeTarget ) {
+                       return uri;
+               }
+
+               // target in title param
+               if ( uri.query.title ) {
+                       parts = uri.query.title.match( re );
+                       if ( parts ) {
+                               uri.query.title = parts[ 1 ];
+                               uri.query.target = parts[ 2 ];
                        }
-               } );
+               }
+
+               // target in path
+               parts = mw.Uri.decode( uri.path ).match( re );
+               if ( parts ) {
+                       uri.path = parts[ 1 ];
+                       uri.query.target = parts[ 2 ];
+               }
 
-               // Check all filter interactions
-               this.filtersModel.reassessFilterInteractions();
+               return uri;
        };
 
        /**
-        * Get parameters representing the current state of the model
+        * Get an object representing given parameters that are unrecognized by the model
         *
-        * @return {Object} Uri query parameters
+        * @param  {Object} params Full params object
+        * @return {Object} Unrecognized params
         */
-       mw.rcfilters.UriProcessor.prototype.getUriParametersFromModel = function () {
-               return $.extend(
-                       true,
-                       {},
-                       this.filtersModel.getParametersFromFilters(),
-                       this.filtersModel.getHighlightParameters(),
-                       {
-                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ),
-                               invert: String( Number( this.filtersModel.areNamespacesInverted() ) )
+       mw.rcfilters.UriProcessor.prototype.getUnrecognizedParams = function ( params ) {
+               // Start with full representation
+               var givenParamNames = Object.keys( params ),
+                       unrecognizedParams = $.extend( true, {}, params );
+
+               // Extract unrecognized parameters
+               Object.keys( this.filtersModel.getEmptyParameterState() ).forEach( function ( paramName ) {
+                       // Remove recognized params
+                       if ( givenParamNames.indexOf( paramName ) > -1 ) {
+                               delete unrecognizedParams[ paramName ];
                        }
-               );
+               } );
+
+               return unrecognizedParams;
        };
 
        /**
-        * Build the full parameter representation based on given query parameters
+        * Update the URL of the page to reflect current filters
         *
-        * @private
-        * @param {Object} uriQuery Given URI query
-        * @return {Object} Full parameter state representing the URI query
+        * This should not be called directly from outside the controller.
+        * If an action requires changing the URL, it should either use the
+        * highlighting actions below, or call #updateChangesList which does
+        * the uri corrections already.
+        *
+        * @param {Object} [params] Extra parameters to add to the API call
         */
-       mw.rcfilters.UriProcessor.prototype._expandModelParameters = function ( uriQuery ) {
-               var filterRepresentation = this.filtersModel.getFiltersFromParameters( uriQuery );
+       mw.rcfilters.UriProcessor.prototype.updateURL = function ( params ) {
+               var currentUri = new mw.Uri(),
+                       updatedUri = this.getUpdatedUri();
+
+               updatedUri.extend( params || {} );
+
+               if (
+                       this.getVersion( currentUri.query ) !== 2 ||
+                       this.isNewState( currentUri.query, updatedUri.query )
+               ) {
+                       this.constructor.static.replaceState( updatedUri );
+               }
+       };
 
-               return $.extend( true,
-                       {},
-                       uriQuery,
-                       this.filtersModel.getParametersFromFilters( filterRepresentation ),
-                       this.filtersModel.extractHighlightValues( uriQuery ),
-                       {
-                               highlight: String( Number( uriQuery.highlight ) ),
-                               invert: String( Number( uriQuery.invert ) )
-                       }
+       /**
+        * Update the filters model based on the URI query
+        * This happens on initialization, and from this moment on,
+        * we consider the system synchronized, and the model serves
+        * as the source of truth for the URL.
+        *
+        * This methods should only be called once on initialization.
+        * After initialization, the model updates the URL, not the
+        * other way around.
+        *
+        * @param {Object} [uriQuery] URI query
+        */
+       mw.rcfilters.UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
+               uriQuery = uriQuery || this._normalizeTargetInUri( new mw.Uri() ).query;
+               this.filtersModel.updateStateFromParams(
+                       this._getNormalizedQueryParams( uriQuery )
                );
        };
 
                // This will allow us to always have a proper check of whether
                // the requested new url is one to change or not, regardless of
                // actual parameter visibility/representation in the URL
-               currentParamState = this._expandModelParameters( currentUriQuery );
-               updatedParamState = this._expandModelParameters( updatedUriQuery );
+               currentParamState = $.extend(
+                       true,
+                       {},
+                       this.filtersModel.getMinimizedParamRepresentation( currentUriQuery ),
+                       this.getUnrecognizedParams( currentUriQuery )
+               );
+               updatedParamState = $.extend(
+                       true,
+                       {},
+                       this.filtersModel.getMinimizedParamRepresentation( updatedUriQuery ),
+                       this.getUnrecognizedParams( updatedUriQuery )
+               );
 
                return notEquivalent( currentParamState, updatedParamState );
        };
         */
        mw.rcfilters.UriProcessor.prototype.doesQueryContainRecognizedParams = function ( uriQuery ) {
                var anyValidInUrl,
-                       validParameterNames = Object.keys( this._getEmptyParameterState() )
-                               .filter( function ( param ) {
-                                       // Remove 'highlight' parameter from this check;
-                                       // if it's the only parameter in the URL we still
-                                       // want to consider the URL 'empty' for defaults to load
-                                       return param !== 'highlight';
-                               } );
+                       validParameterNames = Object.keys( this.filtersModel.getEmptyParameterState() );
 
                uriQuery = uriQuery || new mw.Uri().query;
 
                return anyValidInUrl || this.getVersion( uriQuery ) === 2;
        };
 
-       /**
-        * Remove all parameters that have the same value as the base state
-        * This method expects uri queries of the urlversion=2 format
-        *
-        * @private
-        * @param {Object} uriQuery Current uri query
-        * @return {Object} Minimized query
-        */
-       mw.rcfilters.UriProcessor.prototype.minimizeQuery = function ( uriQuery ) {
-               var baseParams = this._getEmptyParameterState(),
-                       uriResult = $.extend( true, {}, uriQuery );
-
-               $.each( uriResult, function ( paramName, paramValue ) {
-                       if (
-                               baseParams[ paramName ] !== undefined &&
-                               baseParams[ paramName ] === paramValue
-                       ) {
-                               // Remove parameter from query
-                               delete uriResult[ paramName ];
-                       }
-               } );
-
-               return uriResult;
-       };
-
        /**
         * Get the adjusted URI params based on the url version
         * If the urlversion is not 2, the parameters are merged with
         * the model's defaults.
+        * Always merge in the hidden parameter defaults.
         *
         * @private
         * @param {Object} uriQuery Current URI query
                // wiki default.
                // Any subsequent change of the URL through the RCFilters
                // system will receive 'urlversion=2'
-               var hiddenParamDefaults = {},
-                       base = this.getVersion( uriQuery ) === 2 ?
-                               {} :
-                               this.filtersModel.getDefaultParams();
-
-               // Go over the model and get all hidden parameters' defaults
-               // These defaults should be applied regardless of the urlversion
-               // but be overridden by the URL params if they exist
-               $.each( this.filtersModel.getFilterGroups(), function ( groupName, groupModel ) {
-                       if ( groupModel.isHidden() ) {
-                               $.extend( true, hiddenParamDefaults, groupModel.getDefaultParams() );
-                       }
-               } );
-
-               return this.minimizeQuery(
-                       $.extend( true, {}, hiddenParamDefaults, base, uriQuery, { urlversion: '2' } )
-               );
-       };
+               var base = this.getVersion( uriQuery ) === 2 ?
+                       {} :
+                       this.filtersModel.getDefaultParams();
 
-       /**
-        * Get the representation of an empty parameter state
-        *
-        * @private
-        * @return {Object} Empty parameter state
-        */
-       mw.rcfilters.UriProcessor.prototype._getEmptyParameterState = function () {
-               // Override empty parameter state with the sticky parameter values
-               return $.extend( true, {}, this.emptyParameterState, this.filtersModel.getStickyParams() );
-       };
-
-       /**
-        * Build an empty representation of the parameters, where all parameters
-        * are either set to '0' or '' depending on their type.
-        * This must run during initialization, before highlights are set.
-        *
-        * @private
-        */
-       mw.rcfilters.UriProcessor.prototype._buildEmptyParameterState = function () {
-               var emptyParams = this.filtersModel.getParametersFromFilters( {} ),
-                       emptyHighlights = this.filtersModel.getHighlightParameters();
-
-               this.emptyParameterState = $.extend(
+               return $.extend(
                        true,
                        {},
-                       emptyParams,
-                       emptyHighlights,
-                       { highlight: '0', invert: '0' }
+                       this.filtersModel.getMinimizedParamRepresentation(
+                               $.extend( true, {}, base, uriQuery )
+                       ),
+                       { urlversion: '2' }
                );
        };
 }( mediaWiki, jQuery ) );