X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki.rcfilters%2Fmw.rcfilters.Controller.js;h=685adb681143c35a4876c7111f415261f4eb2c1b;hb=31495b9e60e01438f5906cb24d484599e4930e66;hp=ee74ac5f97d69bff0cf5d30c10a1090a38ebc207;hpb=ce4b763f7644de9309974d014f9e8824b9fa24dc;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js index ee74ac5f97..685adb6811 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js @@ -21,6 +21,7 @@ this.baseFilterState = {}; this.uriProcessor = null; this.initializing = false; + this.wereSavedQueriesSaved = false; this.prevLoggedItems = []; @@ -201,8 +202,6 @@ // Initialize the model this.filtersModel.initializeFilters( filterStructure, views ); - this._buildBaseFilterState(); - this.uriProcessor = new mw.rcfilters.UriProcessor( this.filtersModel ); @@ -214,15 +213,13 @@ 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. @@ -263,7 +260,10 @@ this.initializing = false; this.switchView( 'default' ); - this._scheduleLiveUpdate(); + this.pollingRate = mw.config.get( 'StructuredChangeFiltersLiveUpdatePollingRate' ); + if ( this.pollingRate ) { + this._scheduleLiveUpdate(); + } }; /** @@ -366,6 +366,31 @@ 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 */ @@ -503,7 +528,7 @@ * @private */ mw.rcfilters.Controller.prototype._scheduleLiveUpdate = function () { - setTimeout( this._doLiveUpdate.bind( this ), 3000 ); + setTimeout( this._doLiveUpdate.bind( this ), this.pollingRate * 1000 ); }; /** @@ -518,14 +543,14 @@ } this._checkForNewChanges() - .then( function ( data ) { + .then( function ( newChanges ) { if ( !this._shouldCheckForNewChanges() ) { // by the time the response is received, // it may not be appropriate anymore return; } - if ( data.changes !== 'NO_RESULTS' ) { + if ( newChanges ) { if ( this.changesListModel.getLiveUpdate() ) { return this.updateChangesList( null, this.LIVE_UPDATE ); } else { @@ -551,19 +576,21 @@ /** * Check if new changes, newer than those currently shown, are available * - * @return {jQuery.Promise} Promise object that resolves after trying - * to fetch 1 change newer than the last known 'from' parameter value + * @return {jQuery.Promise} Promise object that resolves with a bool + * specifying if there are new changes or not * * @private */ mw.rcfilters.Controller.prototype._checkForNewChanges = function () { - return this._fetchChangesList( - 'liveUpdate', - { - limit: 1, - // temporarily disabled ( T173613#3591657 ) - // peek: 1, // bypasses all UI - from: this.changesListModel.getNextFrom() + var params = { + limit: 1, + peek: 1, // bypasses ChangesList specific UI + from: this.changesListModel.getNextFrom() + }; + return this._queryChangesList( 'liveUpdate', params ).then( + function ( data ) { + // no result is 204 with the 'peek' param + return data.status === 200; } ); }; @@ -585,36 +612,36 @@ * @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, + { + invert: String( Number( this.filtersModel.areNamespacesInverted() ) ), + highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ) + }, + this.filtersModel.getParametersFromFilters( selectedState ) + ), + highlights: highlightedItems + }, + setAsDefault ); - if ( setAsDefault ) { - this.savedQueriesModel.setDefault( queryID ); - } - // Save item this._saveSavedQueries(); }; @@ -662,8 +689,9 @@ * @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 ( @@ -675,25 +703,26 @@ 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 ) ); + this.filtersModel.toggleInvertedNamespaces( !!Number( data.params.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 { @@ -723,9 +752,8 @@ // 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' @@ -733,9 +761,15 @@ return this.savedQueriesModel.findMatchingQuery( { - filters: selectedState, - highlights: highlightedItems, - invert: this.filtersModel.areNamespacesInverted() + params: $.extend( + true, + { + highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ), + invert: String( Number( this.filtersModel.areNamespacesInverted() ) ) + }, + this.filtersModel.getParametersFromFilters( selectedState ) + ), + highlights: highlightedItems } ); }; @@ -752,113 +786,14 @@ } ); }; - /** - * 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 ); @@ -868,10 +803,24 @@ 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; }; /** @@ -1035,31 +984,24 @@ * @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, invert: data.params.invert } + ); } - return this.filtersModel.getDefaultParams(); }; @@ -1115,22 +1057,20 @@ }; /** - * Fetch the list of changes from the server for the current filters + * Query the list of changes from the server for the current filters * - * @param {string} [counterId='updateChangesList'] Id for this request. To allow concurrent requests + * @param {string} counterId Id for this request. To allow concurrent requests * not to invalidate each other. * @param {Object} [params={}] Parameters to add to the query * - * @return {jQuery.Promise} Promise object that will resolve with the changes list - * or with a string denoting no results. + * @return {jQuery.Promise} Promise object resolved with { content, status } */ - mw.rcfilters.Controller.prototype._fetchChangesList = function ( counterId, params ) { + mw.rcfilters.Controller.prototype._queryChangesList = function ( counterId, params ) { var uri = this._getUpdatedUri(), stickyParams = this.filtersModel.getStickyParams(), requestId, latestRequest; - counterId = counterId || 'updateChangesList'; params = params || {}; params.action = 'render'; // bypasses MW chrome @@ -1152,53 +1092,52 @@ return $.ajax( uri.toString(), { contentType: 'html' } ) .then( - function ( html, reason ) { - var $parsed, - pieces; - + function ( content, message, jqXHR ) { if ( !latestRequest() ) { return $.Deferred().reject(); } - - if ( params.peek && reason === 'notmodified' ) { - return { - changes: 'NO_RESULTS' - }; + return { + content: content, + status: jqXHR.status + }; + }, + // RC returns 404 when there is no results + function ( jqXHR ) { + if ( latestRequest() ) { + return $.Deferred().resolve( + { + content: jqXHR.responseText, + status: jqXHR.status + } + ).promise(); } + } + ); + }; - // Because of action=render, the response is a list of nodes. - // It has to be put under a root node so it can be queried. - $parsed = $( '
' ).append( $( $.parseHTML( html ) ) ); - - pieces = { - // Changes list - changes: $parsed.find( '.mw-changeslist' ).first().contents(), - // Fieldset - fieldset: $parsed.find( 'fieldset.cloptions' ).first() - }; + /** + * Fetch the list of changes from the server for the current filters + * + * @return {jQuery.Promise} Promise object that will resolve with the changes list + * and the fieldset. + */ + mw.rcfilters.Controller.prototype._fetchChangesList = function () { + return this._queryChangesList( 'updateChangesList' ) + .then( + function ( data ) { + var $parsed = $( '
' ).append( $( $.parseHTML( data.content ) ) ), + pieces = { + // Changes list + changes: $parsed.find( '.mw-changeslist' ).first().contents(), + // Fieldset + 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; - }, - // RC returns 404 when there is no results - 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.cloptions' ).first() - } ).promise(); } ); };