Merge "Skin: Make skins aware of their registered skin name"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.Controller.js
index b07df57..6da8119 100644 (file)
@@ -21,6 +21,7 @@
                this.baseFilterState = {};
                this.uriProcessor = null;
                this.initializing = false;
+               this.wereSavedQueriesSaved = false;
 
                this.prevLoggedItems = [];
 
@@ -42,6 +43,7 @@
        mw.rcfilters.Controller.prototype.initialize = function ( filterStructure, namespaceStructure, tagList ) {
                var parsedSavedQueries,
                        displayConfig = mw.config.get( 'StructuredChangeFiltersDisplayConfig' ),
+                       defaultSavedQueryExists = mw.config.get( 'wgStructuredChangeFiltersDefaultSavedQueryExists' ),
                        controller = this,
                        views = {},
                        items = [],
                                        separator: ';',
                                        fullCoverage: true,
                                        filters: items
+                               },
+                               {
+                                       name: 'invertGroup',
+                                       type: 'boolean',
+                                       hidden: true,
+                                       filters: [ {
+                                               name: 'invert',
+                                               'default': '0'
+                                       } ]
                                } ]
                        };
                }
                // Initialize the model
                this.filtersModel.initializeFilters( filterStructure, views );
 
-               this._buildBaseFilterState();
-
                this.uriProcessor = new mw.rcfilters.UriProcessor(
                        this.filtersModel
                );
                                parsedSavedQueries = {};
                        }
 
-                       // The queries are saved in a minimized state, so we need
-                       // to send over the base state so the saved queries model
-                       // can normalize them per each query item
-                       this.savedQueriesModel.initialize(
-                               parsedSavedQueries,
-                               this._getBaseFilterState(),
-                               // This is for backwards compatibility - delete all excluded filter states
-                               Object.keys( this.filtersModel.getExcludedFiltersState() )
-                       );
+                       // Initialize saved queries
+                       this.savedQueriesModel.initialize( parsedSavedQueries );
+                       if ( this.savedQueriesModel.isConverted() ) {
+                               // Since we know we converted, we're going to re-save
+                               // the queries so they are now migrated to the new format
+                               this._saveSavedQueries();
+                       }
                }
 
                // Check whether we need to load defaults.
                // Defaults should only be applied on load (if necessary)
                // or on request
                this.initializing = true;
-               if (
-                       !mw.user.isAnon() && this.savedQueriesModel.getDefault() &&
-                       !this.uriProcessor.doesQueryContainRecognizedParams( uri.query )
-               ) {
-                       // We have defaults from a saved query.
-                       // We will load them straight-forward (as if
-                       // they were clicked in the menu) so we trigger
-                       // a full ajax request and change of URL
+
+               if ( defaultSavedQueryExists ) {
+                       // This came from the server, meaning that we have a default
+                       // saved query, but the server could not load it, probably because
+                       // it was pre-conversion to the new format.
+                       // We need to load this query again
                        this.applySavedQuery( this.savedQueriesModel.getDefault() );
                } else {
                        // There are either recognized parameters in the URL
                this.initializing = false;
                this.switchView( 'default' );
 
-               this._scheduleLiveUpdate();
+               this.pollingRate = mw.config.get( 'StructuredChangeFiltersLiveUpdatePollingRate' );
+               if ( this.pollingRate ) {
+                       this._scheduleLiveUpdate();
+               }
        };
 
        /**
                this.updateChangesList();
        };
 
+       /**
+        * Check whether the default values of the filters are all false.
+        *
+        * @return {boolean} Defaults are all false
+        */
+       mw.rcfilters.Controller.prototype.areDefaultsEmpty = function () {
+               var defaultParams = this._getDefaultParams(),
+                       defaultFilters = this.filtersModel.getFiltersFromParameters( defaultParams );
+
+               this._deleteExcludedValuesFromFilterState( defaultFilters );
+
+               if ( Object.keys( defaultParams ).some( function ( paramName ) {
+                       return paramName.endsWith( '_color' ) && defaultParams[ paramName ] !== null;
+               } ) ) {
+                       // There are highlights in the defaults, they're definitely
+                       // not empty
+                       return false;
+               }
+
+               // Defaults can change in a session, so we need to do this every time
+               return Object.keys( defaultFilters ).every( function ( filterName ) {
+                       return !defaultFilters[ filterName ];
+               } );
+       };
+
        /**
         * Empty all selected filters
         */
         * @private
         */
        mw.rcfilters.Controller.prototype._scheduleLiveUpdate = function () {
-               setTimeout( this._doLiveUpdate.bind( this ), 3000 );
+               setTimeout( this._doLiveUpdate.bind( this ), this.pollingRate * 1000 );
        };
 
        /**
         * @param {boolean} [setAsDefault=false] This query should be set as the default
         */
        mw.rcfilters.Controller.prototype.saveCurrentQuery = function ( label, setAsDefault ) {
-               var queryID,
-                       highlightedItems = {},
+               var highlightedItems = {},
                        highlightEnabled = this.filtersModel.isHighlightEnabled(),
                        selectedState = this.filtersModel.getSelectedState();
 
                // Prepare highlights
                this.filtersModel.getHighlightedItems().forEach( function ( item ) {
-                       highlightedItems[ item.getName() ] = highlightEnabled ?
+                       highlightedItems[ item.getName() + '_color' ] = highlightEnabled ?
                                item.getHighlightColor() : null;
                } );
-               // These are filter states; highlight is stored as boolean
-               highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
                // Delete all excluded filters
                this._deleteExcludedValuesFromFilterState( selectedState );
 
                // Add item
-               queryID = this.savedQueriesModel.addNewQuery(
+               this.savedQueriesModel.addNewQuery(
                        label || mw.msg( 'rcfilters-savedqueries-defaultlabel' ),
                        {
-                               filters: selectedState,
-                               highlights: highlightedItems,
-                               invert: this.filtersModel.areNamespacesInverted()
-                       }
+                               params: $.extend(
+                                       true,
+                                       {
+                                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
+                                       },
+                                       this.filtersModel.getParametersFromFilters( selectedState )
+                               ),
+                               highlights: highlightedItems
+                       },
+                       setAsDefault
                );
 
-               if ( setAsDefault ) {
-                       this.savedQueriesModel.setDefault( queryID );
-               }
-
                // Save item
                this._saveSavedQueries();
        };
         * @param {string} queryID Query id
         */
        mw.rcfilters.Controller.prototype.applySavedQuery = function ( queryID ) {
-               var data, highlights,
+               var highlights,
                        queryItem = this.savedQueriesModel.getItemByID( queryID ),
+                       data = this.savedQueriesModel.getItemFullData( queryID ),
                        currentMatchingQuery = this.findQueryMatchingCurrentState();
 
                if (
                                currentMatchingQuery.getID() !== queryItem.getID()
                        )
                ) {
-                       data = queryItem.getData();
                        highlights = data.highlights;
 
-                       // Backwards compatibility; initial version mispelled 'highlight' with 'highlights'
-                       highlights.highlight = highlights.highlights || highlights.highlight;
-
                        // Update model state from filters
                        this.filtersModel.toggleFiltersSelected(
                                // Merge filters with excluded values
-                               $.extend( true, {}, data.filters, this.filtersModel.getExcludedFiltersState() )
+                               $.extend(
+                                       true,
+                                       {},
+                                       this.filtersModel.getFiltersFromParameters( data.params ),
+                                       this.filtersModel.getExcludedFiltersState()
+                               )
                        );
 
-                       // Update namespace inverted property
-                       this.filtersModel.toggleInvertedNamespaces( !!Number( data.invert ) );
-
                        // Update highlight state
-                       this.filtersModel.toggleHighlight( !!Number( highlights.highlight ) );
+                       this.filtersModel.toggleHighlight( !!Number( data.params.highlight ) );
                        this.filtersModel.getItems().forEach( function ( filterItem ) {
-                               var color = highlights[ filterItem.getName() ];
+                               var color = highlights[ filterItem.getName() + '_color' ];
                                if ( color ) {
                                        filterItem.setHighlightColor( color );
                                } else {
 
                // Prepare highlights of the current query
                this.filtersModel.getItemsSupportingHighlights().forEach( function ( item ) {
-                       highlightedItems[ item.getName() ] = item.getHighlightColor();
+                       highlightedItems[ item.getName() + '_color' ] = item.getHighlightColor();
                } );
-               highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
                // Remove anything that should be excluded from the saved query
                // this includes sticky filters and filters marked with 'excludedFromSavedQueries'
 
                return this.savedQueriesModel.findMatchingQuery(
                        {
-                               filters: selectedState,
-                               highlights: highlightedItems,
-                               invert: this.filtersModel.areNamespacesInverted()
+                               params: $.extend(
+                                       true,
+                                       {
+                                               highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
+                                       },
+                                       this.filtersModel.getParametersFromFilters( selectedState )
+                               ),
+                               highlights: highlightedItems
                        }
                );
        };
                } );
        };
 
-       /**
-        * Get an object representing the base state of parameters
-        * and highlights.
-        *
-        * This is meant to make sure that the saved queries that are
-        * in memory are always the same structure as what we would get
-        * by calling the current model's "getSelectedState" and by checking
-        * highlight items.
-        *
-        * In cases where a user saved a query when the system had a certain
-        * set of filters, and then a filter was added to the system, we want
-        * to make sure that the stored queries can still be comparable to
-        * the current state, which means that we need the base state for
-        * two operations:
-        *
-        * - Saved queries are stored in "minimal" view (only changed filters
-        *   are stored); When we initialize the system, we merge each minimal
-        *   query with the base state (using 'getNormalizedFilters') so all
-        *   saved queries have the exact same structure as what we would get
-        *   by checking the getSelectedState of the filter.
-        * - When we save the queries, we minimize the object to only represent
-        *   whatever has actually changed, rather than store the entire
-        *   object. To check what actually is different so we can store it,
-        *   we need to obtain a base state to compare against, this is
-        *   what #_getMinimalFilterList does
-        */
-       mw.rcfilters.Controller.prototype._buildBaseFilterState = function () {
-               var defaultParams = this.filtersModel.getDefaultParams(),
-                       highlightedItems = {};
-
-               // Prepare highlights
-               this.filtersModel.getItemsSupportingHighlights().forEach( function ( item ) {
-                       highlightedItems[ item.getName() ] = null;
-               } );
-               highlightedItems.highlight = false;
-
-               this.baseFilterState = {
-                       filters: this.filtersModel.getFiltersFromParameters( defaultParams ),
-                       highlights: highlightedItems,
-                       invert: false
-               };
-       };
-
-       /**
-        * 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: {
-        *        filterName: (bool)
-        *    },
-        *    highlights: {
-        *        filterName: (string|null)
-        *    }
-        * }
-        *
-        * @return {Object} Object representing the base state of
-        *  parameters and highlights
-        */
-       mw.rcfilters.Controller.prototype._getBaseFilterState = function () {
-               return this.baseFilterState;
-       };
-
-       /**
-        * Get an object that holds only the parameters and highlights that have
-        * values different than the base default value.
-        *
-        * This is the reverse of the normalization we do initially on loading and
-        * initializing the saved queries model.
-        *
-        * @param {Object} valuesObject Object representing the state of both
-        *  filters and highlights in its normalized version, to be minimized.
-        * @return {Object} Minimal filters and highlights list
-        */
-       mw.rcfilters.Controller.prototype._getMinimalFilterList = function ( valuesObject ) {
-               var result = { filters: {}, highlights: {}, invert: valuesObject.invert },
-                       baseState = this._getBaseFilterState();
-
-               // XOR results
-               $.each( valuesObject.filters, function ( name, value ) {
-                       if ( baseState.filters !== undefined && baseState.filters[ name ] !== value ) {
-                               result.filters[ name ] = value;
-                       }
-               } );
-
-               $.each( valuesObject.highlights, function ( name, value ) {
-                       if ( baseState.highlights !== undefined && baseState.highlights[ name ] !== value ) {
-                               result.highlights[ name ] = value;
-                       }
-               } );
-
-               return result;
-       };
-
        /**
         * Save the current state of the saved queries model with all
         * query item representation in the user settings.
         */
        mw.rcfilters.Controller.prototype._saveSavedQueries = function () {
-               var stringified,
-                       state = this.savedQueriesModel.getState(),
-                       controller = this;
-
-               // Minimize before save
-               $.each( state.queries, function ( queryID, info ) {
-                       state.queries[ queryID ].data = controller._getMinimalFilterList( info.data );
-               } );
+               var stringified, oldPrefValue,
+                       backupPrefName = this.savedQueriesPreferenceName + '-versionbackup',
+                       state = this.savedQueriesModel.getState();
 
                // Stringify state
                stringified = JSON.stringify( state );
                        return;
                }
 
+               if ( !this.wereSavedQueriesSaved && this.savedQueriesModel.isConverted() ) {
+                       // The queries were converted from the previous version
+                       // Keep the old string in the [prefname]-versionbackup
+                       oldPrefValue = mw.user.options.get( this.savedQueriesPreferenceName );
+
+                       // Save the old preference in the backup preference
+                       new mw.Api().saveOption( backupPrefName, oldPrefValue );
+                       // Update the preference for this session
+                       mw.user.options.set( backupPrefName, oldPrefValue );
+               }
+
                // Save the preference
                new mw.Api().saveOption( this.savedQueriesPreferenceName, stringified );
                // Update the preference for this session
                mw.user.options.set( this.savedQueriesPreferenceName, stringified );
+
+               // Tag as already saved so we don't do this again
+               this.wereSavedQueriesSaved = true;
        };
 
        /**
         * @return {Object} Default parameters
         */
        mw.rcfilters.Controller.prototype._getDefaultParams = function () {
-               var data, queryHighlights,
-                       savedParams = {},
-                       savedHighlights = {},
-                       defaultSavedQueryItem = !mw.user.isAnon() && this.savedQueriesModel.getItemByID( this.savedQueriesModel.getDefault() );
-
-               if ( defaultSavedQueryItem ) {
-                       data = defaultSavedQueryItem.getData();
-
-                       queryHighlights = data.highlights || {};
-                       savedParams = this.filtersModel.getParametersFromFilters(
-                               // Merge filters with sticky values
-                               $.extend( true, {}, data.filters, this.filtersModel.getStickyFiltersState() )
+               var savedFilters,
+                       data = ( !mw.user.isAnon() && this.savedQueriesModel.getItemFullData( this.savedQueriesModel.getDefault() ) ) || {};
+
+               if ( !$.isEmptyObject( data ) ) {
+                       // Merge saved filter state with sticky filter values
+                       savedFilters = $.extend(
+                               true, {},
+                               this.filtersModel.getFiltersFromParameters( data.params ),
+                               this.filtersModel.getStickyFiltersState()
                        );
 
-                       // Translate highlights to parameters
-                       savedHighlights.highlight = String( Number( queryHighlights.highlight ) );
-                       $.each( queryHighlights, function ( filterName, color ) {
-                               if ( filterName !== 'highlights' ) {
-                                       savedHighlights[ filterName + '_color' ] = color;
-                               }
-                       } );
-
-                       return $.extend( true, {}, savedParams, savedHighlights, { invert: String( Number( data.invert || 0 ) ) } );
+                       // Return parameter representation
+                       return $.extend( true, {},
+                               this.filtersModel.getParametersFromFilters( savedFilters ),
+                               data.highlights,
+                               { highlight: data.params.highlight }
+                       );
                }
-
                return this.filtersModel.getDefaultParams();
        };