Merge "Remove the localStorage replication of the block cookie"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.Controller.js
index 1df31a2..e034826 100644 (file)
        /**
         * Initialize the filter and parameter states
         *
-        * @param {Object} filterStructure Filter definition and structure for the model
+        * @param {Array} filterStructure Filter definition and structure for the model
         */
        mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
-               var uri = new mw.Uri();
-
+               var $changesList = $( '.mw-changeslist' ).first().contents();
                // Initialize the model
                this.filtersModel.initializeFilters( filterStructure );
+               this.updateStateBasedOnUrl();
+
+               // Update the changes list with the existing data
+               // so it gets processed
+               this.changesListModel.update(
+                       $changesList.length ? $changesList : 'NO_RESULTS',
+                       $( 'fieldset.rcoptions' ).first()
+               );
+
+       };
+
+       /**
+        * Update filter state (selection and highlighting) based
+        * on current URL and default values.
+        */
+       mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
+               var uri = new mw.Uri();
 
                // Set filter states based on defaults and URL params
-               this.filtersModel.updateFilters(
+               this.filtersModel.toggleFiltersSelected(
                        this.filtersModel.getFiltersFromParameters(
                                // Merge defaults with URL params for initialization
                                $.extend(
                this.filtersModel.toggleHighlight( !!uri.query.highlight );
                this.filtersModel.getItems().forEach( function ( filterItem ) {
                        var color = uri.query[ filterItem.getName() + '_color' ];
-                       if ( !color ) {
-                               return;
+                       if ( color ) {
+                               filterItem.setHighlightColor( color );
+                       } else {
+                               filterItem.clearHighlightColor();
                        }
-
-                       filterItem.setHighlightColor( color );
                } );
 
                // Check all filter interactions
         */
        mw.rcfilters.Controller.prototype.resetToDefaults = function () {
                this.filtersModel.setFiltersToDefaults();
+               this.filtersModel.clearAllHighlightColors();
                // Check all filter interactions
                this.filtersModel.reassessFilterInteractions();
 
-               this.updateURL();
                this.updateChangesList();
        };
 
         * Empty all selected filters
         */
        mw.rcfilters.Controller.prototype.emptyFilters = function () {
+               var highlightedFilterNames = this.filtersModel
+                       .getHighlightedItems()
+                       .map( function ( filterItem ) { return { name: filterItem.getName() }; } );
+
                this.filtersModel.emptyAllFilters();
                this.filtersModel.clearAllHighlightColors();
                // Check all filter interactions
                this.filtersModel.reassessFilterInteractions();
 
-               this.updateURL();
                this.updateChangesList();
+
+               if ( highlightedFilterNames ) {
+                       this.trackHighlight( 'clearAll', highlightedFilterNames );
+               }
        };
 
        /**
-        * Update the state of a filter
+        * Update the selected state of a filter
         *
         * @param {string} filterName Filter name
-        * @param {boolean} isSelected Filter selected state
+        * @param {boolean} [isSelected] Filter selected state
         */
-       mw.rcfilters.Controller.prototype.updateFilter = function ( filterName, isSelected ) {
-               var obj = {},
-                       filterItem = this.filtersModel.getItemByName( filterName );
+       mw.rcfilters.Controller.prototype.toggleFilterSelect = function ( filterName, isSelected ) {
+               var filterItem = this.filtersModel.getItemByName( filterName );
+
+               isSelected = isSelected === undefined ? !filterItem.isSelected() : isSelected;
 
                if ( filterItem.isSelected() !== isSelected ) {
-                       obj[ filterName ] = isSelected;
-                       this.filtersModel.updateFilters( obj );
+                       this.filtersModel.toggleFilterSelected( filterName, isSelected );
 
-                       this.updateURL();
                        this.updateChangesList();
 
                        // Check filter interactions
-                       this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
+                       this.filtersModel.reassessFilterInteractions( filterItem );
                }
        };
 
        /**
         * Update the URL of the page to reflect current filters
+        *
+        * 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.
+        *
+        * @private
+        * @param {Object} [params] Extra parameters to add to the API call
         */
-       mw.rcfilters.Controller.prototype.updateURL = function () {
-               var uri = this.getUpdatedUri();
-               window.history.pushState( { tag: 'rcfilters' }, document.title, uri.toString() );
+       mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
+               var updatedUri,
+                       notEquivalent = function ( obj1, obj2 ) {
+                               var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
+                               return keys.some( function ( key ) {
+                                       return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
+                               } );
+                       };
+
+               params = params || {};
+
+               updatedUri = this.getUpdatedUri();
+               updatedUri.extend( params );
+
+               if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
+                       window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+               }
        };
 
        /**
         * Fetch the list of changes from the server for the current filters
         *
         * @return {jQuery.Promise} Promise object that will resolve with the changes list
+        *  or with a string denoting no results.
         */
        mw.rcfilters.Controller.prototype.fetchChangesList = function () {
                var uri = this.getUpdatedUri(),
                        latestRequest = function () {
                                return requestId === this.requestCounter;
                        }.bind( this );
-               uri.extend( this.filtersModel.getParametersFromFilters() );
+
                return $.ajax( uri.toString(), { contentType: 'html' } )
-                       .then( function ( html ) {
-                               return latestRequest() ?
-                                       $( $.parseHTML( html ) ).find( '.mw-changeslist' ).first().contents() :
-                                       null;
-                       } ).then( null, function () {
-                               return latestRequest() ? 'NO_RESULTS' : null;
-                       } );
+                       .then(
+                               // Success
+                               function ( html ) {
+                                       var $parsed;
+                                       if ( !latestRequest() ) {
+                                               return $.Deferred().reject();
+                                       }
+
+                                       $parsed = $( $.parseHTML( html ) );
+
+                                       return {
+                                               // Changes list
+                                               changes: $parsed.find( '.mw-changeslist' ).first().contents(),
+                                               // Fieldset
+                                               fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
+                                       };
+                               },
+                               // Failure
+                               function ( responseObj ) {
+                                       var $parsed;
+
+                                       if ( !latestRequest() ) {
+                                               return $.Deferred().reject();
+                                       }
+
+                                       $parsed = $( $.parseHTML( responseObj.responseText ) );
+
+                                       // Force a resolve state to this promise
+                                       return $.Deferred().resolve( {
+                                               changes: 'NO_RESULTS',
+                                               fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
+                                       } ).promise();
+                               }
+                       );
        };
 
        /**
         * Update the list of changes and notify the model
+        *
+        * @param {Object} [params] Extra parameters to add to the API call
         */
-       mw.rcfilters.Controller.prototype.updateChangesList = function () {
+       mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
+               this.updateURL( params );
                this.changesListModel.invalidate();
                this.fetchChangesList()
-                       .always( function ( changesListContent ) {
-                               if ( changesListContent ) {
-                                       this.changesListModel.update( changesListContent );
-                               }
-                       }.bind( this ) );
+                       .then(
+                               // Success
+                               function ( pieces ) {
+                                       var $changesListContent = pieces.changes,
+                                               $fieldset = pieces.fieldset;
+                                       this.changesListModel.update( $changesListContent, $fieldset );
+                               }.bind( this )
+                               // Do nothing for failure
+                       );
        };
 
        /**
        mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
                this.filtersModel.setHighlightColor( filterName, color );
                this.updateURL();
+               this.trackHighlight( 'set', { name: filterName, color: color } );
        };
 
        /**
        mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
                this.filtersModel.clearHighlightColor( filterName );
                this.updateURL();
+               this.trackHighlight( 'clear', filterName );
+       };
+
+       /**
+        * Clear both highlight and selection of a filter
+        *
+        * @param {string} filterName Name of the filter item
+        */
+       mw.rcfilters.Controller.prototype.clearFilter = function ( filterName ) {
+               var filterItem = this.filtersModel.getItemByName( filterName ),
+                       isHighlighted = filterItem.isHighlighted();
+
+               if ( filterItem.isSelected() || isHighlighted ) {
+                       this.filtersModel.clearHighlightColor( filterName );
+                       this.filtersModel.toggleFilterSelected( filterName, false );
+                       this.updateChangesList();
+                       this.filtersModel.reassessFilterInteractions( filterItem );
+               }
+
+               if ( isHighlighted ) {
+                       this.trackHighlight( 'clear', filterName );
+               }
+       };
+
+       /**
+        * Synchronize the URL with the current state of the filters
+        * without adding an history entry.
+        */
+       mw.rcfilters.Controller.prototype.replaceUrl = function () {
+               window.history.replaceState(
+                       { tag: 'rcfilters' },
+                       document.title,
+                       this.getUpdatedUri().toString()
+               );
+       };
+
+       /**
+        * Track usage of highlight feature
+        *
+        * @param {string} action
+        * @param {array|object|string} filters
+        */
+       mw.rcfilters.Controller.prototype.trackHighlight = function ( action, filters ) {
+               filters = $.type( filters ) === 'string' ? { name: filters } : filters;
+               filters = $.type( filters ) === 'object' ? [ filters ] : filters;
+               mw.track(
+                       'event.ChangesListHighlights',
+                       {
+                               action: action,
+                               filters: filters
+                       }
+               );
        };
 }( mediaWiki, jQuery ) );