Merge "Add $wgMaxJobDBWriteDuration setting for avoiding replication lag"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.Controller.js
index 4a9e780..375b68b 100644 (file)
@@ -12,7 +12,9 @@
                this.changesListModel = changesListModel;
                this.savedQueriesModel = savedQueriesModel;
                this.requestCounter = 0;
-               this.baseState = {};
+               this.baseFilterState = {};
+               this.emptyParameterState = {};
+               this.initializing = false;
        };
 
        /* Initialization */
         * @param {Array} filterStructure Filter definition and structure for the model
         */
        mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
-               var parsedSavedQueries,
+               var parsedSavedQueries, validParameterNames,
+                       uri = new mw.Uri(),
                        $changesList = $( '.mw-changeslist' ).first().contents();
+
                // Initialize the model
                this.filtersModel.initializeFilters( filterStructure );
 
                this._buildBaseFilterState();
+               this._buildEmptyParameterState();
+               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';
+                       } );
 
                try {
                        parsedSavedQueries = JSON.parse( mw.user.options.get( 'rcfilters-saved-queries' ) || '{}' );
                // can normalize them per each query item
                this.savedQueriesModel.initialize(
                        parsedSavedQueries,
-                       this._getBaseState()
+                       this._getBaseFilterState()
                );
 
-               this.updateStateBasedOnUrl();
+               // Check whether we need to load defaults.
+               // We do this by checking whether the current URI query
+               // contains any parameters recognized by the system.
+               // If it does, we load the given state.
+               // If it doesn't, we have no values at all, and we assume
+               // the user loads the base-page and we load defaults.
+               // Defaults should only be applied on load (if necessary)
+               // or on request
+               if (
+                       Object.keys( uri.query ).some( function ( parameter ) {
+                               return validParameterNames.indexOf( parameter ) > -1;
+                       } )
+               ) {
+                       // There are parameters in the url, update model state
+                       this.updateStateBasedOnUrl();
+               } else {
+                       this.initializing = true;
+                       // No valid parameters are given, load defaults
+                       this._updateModelState(
+                               $.extend(
+                                       true,
+                                       // We've ignored the highlight parameter for the sake
+                                       // of seeing whether we need to apply defaults - but
+                                       // while we do load the defaults, we still want to retain
+                                       // the actual value given in the URL for it on top of the
+                                       // defaults
+                                       { highlight: String( Number( uri.query.highlight ) ) },
+                                       this._getDefaultParams()
+                               )
+                       );
+                       this.updateChangesList();
+                       this.initializing = false;
+               }
 
                // Update the changes list with the existing data
                // so it gets processed
         * Reset to default filters
         */
        mw.rcfilters.Controller.prototype.resetToDefaults = function () {
-               this._updateModelState( this._getDefaultParams() );
+               this._updateModelState( $.extend( true, { highlight: '0' }, this._getDefaultParams() ) );
                this.updateChangesList();
        };
 
                        highlightedItems[ item.getName() ] = highlightEnabled ?
                                item.getHighlightColor() : null;
                } );
-               // Stored as a string '0' or '1'
-               highlightedItems.highlight = String( Number( this.filtersModel.isHighlightEnabled() ) );
+               // These are filter states; highlight is stored as boolean
+               highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
                // Add item
                this.savedQueriesModel.addNewQuery(
                        this.filtersModel.toggleFiltersSelected( data.filters );
 
                        // Update highlight state
-                       this.filtersModel.toggleHighlight( !!highlights.highlight );
+                       this.filtersModel.toggleHighlight( !!Number( highlights.highlight ) );
                        this.filtersModel.getItems().forEach( function ( filterItem ) {
                                var color = highlights[ filterItem.getName() ];
                                if ( color ) {
                } );
                highlightedItems.highlight = false;
 
-               this.baseState = {
+               this.baseFilterState = {
                        filters: this.filtersModel.getFiltersFromParameters( defaultParams ),
                        highlights: highlightedItems
                };
        };
 
        /**
-        * Get an object representing the base state of parameters
-        * and highlights. The structure is similar to what we use
+        * 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.
+        */
+       mw.rcfilters.Controller.prototype._buildEmptyParameterState = function () {
+               var emptyParams = this.filtersModel.getParametersFromFilters( {} ),
+                       emptyHighlights = this.filtersModel.getHighlightParameters();
+
+               this.emptyParameterState = $.extend(
+                       true,
+                       {},
+                       emptyParams,
+                       emptyHighlights,
+                       { highlight: '0' }
+               );
+       };
+
+       /**
+        * Get an object representing the base filter state of both
+        * filters and highlights. The structure is similar to what we use
         * to store each query in the saved queries object:
         * {
         *    filters: {
         * @return {Object} Object representing the base state of
         *  parameters and highlights
         */
-       mw.rcfilters.Controller.prototype._getBaseState = function () {
-               return this.baseState;
+       mw.rcfilters.Controller.prototype._getBaseFilterState = function () {
+               return this.baseFilterState;
+       };
+
+       /**
+        * Get an object representing the base state of parameters
+        * and highlights. The structure is similar to what we use
+        * to store each query in the saved queries object:
+        * {
+        *    param1: "value",
+        *    param2: "value1|value2"
+        * }
+        *
+        * @return {Object} Object representing the base state of
+        *  parameters and highlights
+        */
+       mw.rcfilters.Controller.prototype._getEmptyParameterState = function () {
+               return this.emptyParameterState;
        };
 
        /**
         */
        mw.rcfilters.Controller.prototype._getMinimalFilterList = function ( valuesObject ) {
                var result = { filters: {}, highlights: {} },
-                       baseState = this._getBaseState();
+                       baseState = this._getBaseFilterState();
 
                // XOR results
                $.each( valuesObject.filters, function ( name, value ) {
 
        /**
         * Update filter state (selection and highlighting) based
-        * on current URL and default values.
+        * on current URL values.
         */
        mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
-               var uri = new mw.Uri(),
-                       defaultParams = this._getDefaultParams();
+               var uri = new mw.Uri();
 
-               this._updateModelState( $.extend( {}, defaultParams, uri.query ) );
+               this._updateModelState( uri.query );
                this.updateChangesList();
        };
 
                );
 
                // Update highlight state
-               this.filtersModel.toggleHighlight( !!parameters.highlight );
+               this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
                this.filtersModel.getItems().forEach( function ( filterItem ) {
                        var color = parameters[ filterItem.getName() + '_color' ];
                        if ( color ) {
                        savedParams = this.filtersModel.getParametersFromFilters( data.filters || {} );
 
                        // Translate highlights to parameters
-                       savedHighlights.highlight = queryHighlights.highlight;
+                       savedHighlights.highlight = String( Number( queryHighlights.highlight ) );
                        $.each( queryHighlights, function ( filterName, color ) {
                                if ( filterName !== 'highlights' ) {
                                        savedHighlights[ filterName + '_color' ] = color;
         * @param {Object} [params] Extra parameters to add to the API call
         */
        mw.rcfilters.Controller.prototype._updateURL = function ( params ) {
-               var updatedUri,
+               var currentFilterState, updatedFilterState, updatedUri,
+                       uri = new mw.Uri(),
                        notEquivalent = function ( obj1, obj2 ) {
                                var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
                                return keys.some( function ( key ) {
                updatedUri = this._getUpdatedUri();
                updatedUri.extend( params );
 
-               if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
-                       window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+               // Compare states instead of parameters
+               // 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
+               currentFilterState = this.filtersModel.getFiltersFromParameters( uri.query );
+               updatedFilterState = this.filtersModel.getFiltersFromParameters( updatedUri.query );
+               // HACK: Re-merge extra parameters in
+               // This is a hack and a quickfix; a better, more sustainable
+               // fix is being worked on with a UriProcessor, but for now
+               // we need to make sure the **comparison** of whether currentFilterState
+               // and updatedFilterState differ **includes** the extra parameters in the URL
+               currentFilterState = $.extend( true, {}, uri.query, currentFilterState );
+               updatedFilterState = $.extend( true, {}, updatedUri.query, updatedFilterState );
+
+               // Include highlight states
+               $.extend( true,
+                       currentFilterState,
+                       this.filtersModel.extractHighlightValues( uri.query ),
+                       { highlight: !!Number( uri.query.highlight ) }
+               );
+               $.extend( true,
+                       updatedFilterState,
+                       this.filtersModel.extractHighlightValues( updatedUri.query ),
+                       { highlight: !!Number( updatedUri.query.highlight ) }
+               );
+
+               if ( notEquivalent( currentFilterState, updatedFilterState ) ) {
+                       if ( this.initializing ) {
+                               // Initially, when we just build the first page load
+                               // out of defaults, we want to replace the history
+                               window.history.replaceState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+                       } else {
+                               window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+                       }
                }
        };
 
         */
        mw.rcfilters.Controller.prototype._getUpdatedUri = function () {
                var uri = new mw.Uri(),
-                       highlightParams = this.filtersModel.getHighlightParameters();
-
-               // Add to existing queries in URL
-               // TODO: Clean up the list of filters; perhaps 'falsy' filters
-               // shouldn't appear at all? Or compare to existing query string
-               // and see if current state of a specific filter is needed?
-               uri.extend( this.filtersModel.getParametersFromFilters() );
+                       highlightParams = this.filtersModel.getHighlightParameters(),
+                       modelParameters = this.filtersModel.getParametersFromFilters(),
+                       baseParams = this._getEmptyParameterState();
+
+               // Minimize values of the model parameters; show only the values that
+               // are non-zero. We assume that all parameters that are not literally
+               // showing in the URL are set to zero or empty
+               $.each( modelParameters, function ( paramName, value ) {
+                       if ( baseParams[ paramName ] !== value ) {
+                               uri.query[ paramName ] = value;
+                       } else {
+                               // We need to remove this value from the url
+                               delete uri.query[ paramName ];
+                       }
+               } );
 
                // highlight params
-               uri.query.highlight = Number( this.filtersModel.isHighlightEnabled() );
-               Object.keys( highlightParams ).forEach( function ( paramName ) {
-                       // Always have some value (either the color or null) so that
-                       // if we have something in the URL that doesn't have the highlight
-                       // intentionally, it can override default with highlight.
-                       // Otherwise, the $.extend will always add the highlight that
-                       // exists in the default even if the URL query that is being
-                       // refreshed has different highlights, or has highlights enabled
-                       // but no active highlights anywhere
-                       uri.query[ paramName ] = highlightParams[ paramName ] ?
-                               highlightParams[ paramName ] : null;
+               if ( this.filtersModel.isHighlightEnabled() ) {
+                       uri.query.highlight = Number( this.filtersModel.isHighlightEnabled() );
+               } else {
+                       delete uri.query.highlight;
+               }
+               $.each( highlightParams, function ( paramName, value ) {
+                       // Only output if it is different than the base parameters
+                       if ( baseParams[ paramName ] !== value ) {
+                               uri.query[ paramName ] = value;
+                       } else {
+                               delete uri.query[ paramName ];
+                       }
                } );
 
                return uri;