Merge "jquery.migrate: Instrument with mw.track()"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.Controller.js
index 16822d8..209e7c8 100644 (file)
@@ -8,17 +8,25 @@
         * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
         * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel Changes list view model
         * @param {mw.rcfilters.dm.SavedQueriesModel} savedQueriesModel Saved queries model
+        * @param {Object} config Additional configuration
+        * @cfg {string} savedQueriesPreferenceName Where to save the saved queries
         */
-       mw.rcfilters.Controller = function MwRcfiltersController( filtersModel, changesListModel, savedQueriesModel ) {
+       mw.rcfilters.Controller = function MwRcfiltersController( filtersModel, changesListModel, savedQueriesModel, config ) {
                this.filtersModel = filtersModel;
                this.changesListModel = changesListModel;
                this.savedQueriesModel = savedQueriesModel;
+               this.savedQueriesPreferenceName = config.savedQueriesPreferenceName;
+
                this.requestCounter = {};
                this.baseFilterState = {};
                this.uriProcessor = null;
                this.initializing = false;
 
                this.prevLoggedItems = [];
+
+               this.FILTER_CHANGE = 'filterChange';
+               this.SHOW_NEW_CHANGES = 'showNewChanges';
+               this.LIVE_UPDATE = 'liveUpdate';
        };
 
        /* Initialization */
                                        allowArbitrary: true,
                                        validate: $.isNumeric,
                                        range: {
-                                               min: 1,
+                                               min: 0, // The server normalizes negative numbers to 0 results
                                                max: 1000
                                        },
                                        sortFunc: function ( a, b ) { return Number( a.name ) - Number( b.name ); },
                                        // we should remove all sticky behavior methods completely
                                        // See T172156
                                        // isSticky: true,
-                                       filters: displayConfig.arrayLimit.map( function ( num ) {
+                                       excludedFromSavedQueries: true,
+                                       filters: displayConfig.limitArray.map( function ( num ) {
                                                return controller._createFilterDataFromNumber( num, num );
                                        } )
                                },
                                        validate: $.isNumeric,
                                        range: {
                                                min: 0,
-                                               max: displayConfig.maxLimit
+                                               max: displayConfig.maxDays
                                        },
                                        sortFunc: function ( a, b ) { return Number( a.name ) - Number( b.name ); },
                                        numToLabelFunc: function ( i ) {
                                        'default': mw.user.options.get( 'rcdays', '30' ),
                                        // Temporarily making this not sticky while limit is not sticky, see above
                                        // isSticky: true,
+                                       excludedFromSavedQueries: true,
                                        filters: [
                                                // Hours (1, 2, 6, 12)
                                                0.04166, 0.0833, 0.25, 0.5
                                        // Days
-                                       ].concat( displayConfig.arrayDays )
+                                       ].concat( displayConfig.daysArray )
                                                .map( function ( num ) {
                                                        return controller._createFilterDataFromNumber(
                                                                num,
                );
 
                try {
-                       parsedSavedQueries = JSON.parse( mw.user.options.get( 'rcfilters-saved-queries' ) || '{}' );
+                       parsedSavedQueries = JSON.parse( mw.user.options.get( this.savedQueriesPreferenceName ) || '{}' );
                } catch ( err ) {
                        parsedSavedQueries = {};
                }
                this.savedQueriesModel.initialize(
                        parsedSavedQueries,
                        this._getBaseFilterState(),
-                       // This is for backwards compatibility - delete all sticky filter states
-                       Object.keys( this.filtersModel.getStickyFiltersState() )
+                       // This is for backwards compatibility - delete all excluded filter states
+                       Object.keys( this.filtersModel.getExcludedFiltersState() )
                );
 
                // Check whether we need to load defaults.
                        // so it gets processed
                        this.changesListModel.update(
                                $changesList.length ? $changesList : 'NO_RESULTS',
-                               $( 'fieldset.rcoptions' ).first(),
+                               $( 'fieldset.cloptions' ).first(),
                                true // We're using existing DOM elements
                        );
                }
                this.filtersModel.toggleInvertedNamespaces();
 
                if (
-                       this.filtersModel.getFiltersByView( 'namespaces' )
-                               .filter( function ( filterItem ) {
-                                       return filterItem.isSelected();
-                               } )
-                               .length
+                       this.filtersModel.getFiltersByView( 'namespaces' ).filter(
+                               function ( filterItem ) { return filterItem.isSelected(); }
+                       ).length
                ) {
                        // Only re-fetch results if there are namespace items that are actually selected
                        this.updateChangesList();
        mw.rcfilters.Controller.prototype.toggleLiveUpdate = function ( enable ) {
                this.changesListModel.toggleLiveUpdate( enable );
                if ( this.changesListModel.getLiveUpdate() && this.changesListModel.getNewChangesExist() ) {
-                       this.showNewChanges();
+                       this.updateChangesList( null, this.LIVE_UPDATE );
                }
        };
 
 
                                if ( data.changes !== 'NO_RESULTS' ) {
                                        if ( this.changesListModel.getLiveUpdate() ) {
-                                               return this.updateChangesList( false, null, true, false );
+                                               return this.updateChangesList( null, this.LIVE_UPDATE );
                                        } else {
                                                this.changesListModel.setNewChangesExist( true );
                                        }
         * @private
         */
        mw.rcfilters.Controller.prototype._shouldCheckForNewChanges = function () {
-               var liveUpdateFeatureFlag = mw.config.get( 'wgStructuredChangeFiltersEnableLiveUpdate' ) ||
-                       new mw.Uri().query.liveupdate;
-
                return !document.hidden &&
+                       !this.filtersModel.hasConflict() &&
                        !this.changesListModel.getNewChangesExist() &&
                        !this.updatingChangesList &&
-                       liveUpdateFeatureFlag;
+                       mw.rcfilters.featureFlags.liveUpdate;
        };
 
        /**
         * fetching and showing the new changes
         */
        mw.rcfilters.Controller.prototype.showNewChanges = function () {
-               return this.updateChangesList( false, null, true, true );
+               return this.updateChangesList( null, this.SHOW_NEW_CHANGES );
        };
 
        /**
                // These are filter states; highlight is stored as boolean
                highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
-               // Delete all sticky filters
-               this._deleteStickyValuesFromFilterState( selectedState );
+               // Delete all excluded filters
+               this._deleteExcludedValuesFromFilterState( selectedState );
 
                // Add item
                queryID = this.savedQueriesModel.addNewQuery(
 
                        // Update model state from filters
                        this.filtersModel.toggleFiltersSelected(
-                               // Merge filters with sticky values
-                               $.extend( true, {}, data.filters, this.filtersModel.getStickyFiltersState() )
+                               // Merge filters with excluded values
+                               $.extend( true, {}, data.filters, this.filtersModel.getExcludedFiltersState() )
                        );
 
                        // Update namespace inverted property
                } );
                highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
-               // Remove sticky filters
-               this._deleteStickyValuesFromFilterState( selectedState );
+               // Remove anything that should be excluded from the saved query
+               // this includes sticky filters and filters marked with 'excludedFromSavedQueries'
+               this._deleteExcludedValuesFromFilterState( selectedState );
 
                return this.savedQueriesModel.findMatchingQuery(
                        {
         *
         * @param {Object} filterState Filter state
         */
-       mw.rcfilters.Controller.prototype._deleteStickyValuesFromFilterState = function ( filterState ) {
-               // Remove sticky filters
-               $.each( this.filtersModel.getStickyFiltersState(), function ( filterName ) {
+       mw.rcfilters.Controller.prototype._deleteExcludedValuesFromFilterState = function ( filterState ) {
+               // Remove excluded filters
+               $.each( this.filtersModel.getExcludedFiltersState(), function ( filterName ) {
                        delete filterState[ filterName ];
                } );
        };
         * @return {Object} Minimal filters and highlights list
         */
        mw.rcfilters.Controller.prototype._getMinimalFilterList = function ( valuesObject ) {
-               var result = { filters: {}, highlights: {} },
+               var result = { filters: {}, highlights: {}, invert: valuesObject.invert },
                        baseState = this._getBaseFilterState();
 
                // XOR results
                }
 
                // Save the preference
-               new mw.Api().saveOption( 'rcfilters-saved-queries', stringified );
+               new mw.Api().saveOption( this.savedQueriesPreferenceName, stringified );
                // Update the preference for this session
-               mw.user.options.set( 'rcfilters-saved-queries', stringified );
+               mw.user.options.set( this.savedQueriesPreferenceName, stringified );
        };
 
        /**
        /**
         * Update the list of changes and notify the model
         *
-        * @param {boolean} [updateUrl=true] Whether the URL should be updated with the current state of the filters
         * @param {Object} [params] Extra parameters to add to the API call
-        * @param {boolean} [isLiveUpdate=false] The purpose of this update is to show new results for the same filters
-        * @param {boolean} [invalidateCurrentChanges=true] Invalidate current changes by default (show spinner)
+        * @param {string} [updateMode='filterChange'] One of 'filterChange', 'liveUpdate', 'showNewChanges', 'markSeen'
         * @return {jQuery.Promise} Promise that is resolved when the update is complete
         */
-       mw.rcfilters.Controller.prototype.updateChangesList = function ( updateUrl, params, isLiveUpdate, invalidateCurrentChanges ) {
-               updateUrl = updateUrl === undefined ? true : updateUrl;
-               invalidateCurrentChanges = invalidateCurrentChanges === undefined ? true : invalidateCurrentChanges;
-               if ( updateUrl ) {
+       mw.rcfilters.Controller.prototype.updateChangesList = function ( params, updateMode ) {
+               updateMode = updateMode === undefined ? this.FILTER_CHANGE : updateMode;
+
+               if ( updateMode === this.FILTER_CHANGE ) {
                        this._updateURL( params );
                }
-               if ( invalidateCurrentChanges ) {
+               if ( updateMode === this.FILTER_CHANGE || updateMode === this.SHOW_NEW_CHANGES ) {
                        this.changesListModel.invalidate();
                }
                this.changesListModel.setNewChangesExist( false );
                                function ( pieces ) {
                                        var $changesListContent = pieces.changes,
                                                $fieldset = pieces.fieldset;
-                                       this.changesListModel.update( $changesListContent, $fieldset, false, isLiveUpdate );
+                                       this.changesListModel.update(
+                                               $changesListContent,
+                                               $fieldset,
+                                               false,
+                                               // separator between old and new changes
+                                               updateMode === this.SHOW_NEW_CHANGES || updateMode === this.LIVE_UPDATE
+                                       );
                                }.bind( this )
                                // Do nothing for failure
                        )
 
                return $.ajax( uri.toString(), { contentType: 'html' } )
                        .then(
-                               // Success
                                function ( html ) {
-                                       var $parsed;
+                                       var $parsed,
+                                               pieces;
+
                                        if ( !latestRequest() ) {
                                                return $.Deferred().reject();
                                        }
 
                                        $parsed = $( $.parseHTML( html ) );
 
-                                       return {
+                                       pieces = {
                                                // Changes list
                                                changes: $parsed.find( '.mw-changeslist' ).first().contents(),
                                                // Fieldset
-                                               fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
+                                               fieldset: $parsed.find( 'fieldset.cloptions' ).first()
                                        };
+
+                                       // Watchlist returns 200 when there is no results
+                                       if ( pieces.changes.length === 0 ) {
+                                               pieces.changes = 'NO_RESULTS';
+                                       }
+
+                                       return pieces;
                                },
-                               // Failure
+                               // RC returns 404 when there is no results
                                function ( responseObj ) {
                                        var $parsed;
 
                                        // Force a resolve state to this promise
                                        return $.Deferred().resolve( {
                                                changes: 'NO_RESULTS',
-                                               fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
+                                               fieldset: $parsed.find( 'fieldset.cloptions' ).first()
                                        } ).promise();
                                }
                        );
                        this.prevLoggedItems = filters;
                }
        };
+
+       /**
+        * Mark all changes as seen on Watchlist
+        */
+       mw.rcfilters.Controller.prototype.markAllChangesAsSeen = function () {
+               var api = new mw.Api();
+               api.postWithToken( 'csrf', {
+                       formatversion: 2,
+                       action: 'setnotificationtimestamp',
+                       entirewatchlist: true
+               } ).then( function () {
+                       this.updateChangesList( null, 'markSeen' );
+               }.bind( this ) );
+       };
 }( mediaWiki, jQuery ) );