From: Stephane Bisson Date: Fri, 21 Jul 2017 15:41:36 +0000 (-0400) Subject: RCFilters: show new changes X-Git-Tag: 1.31.0-rc.0~2524^2 X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/banques/ajouter.php?a=commitdiff_plain;h=e2bea6350c19c7133a94a50707b1b2a9599aec66;p=lhc%2Fweb%2Fwiklou.git RCFilters: show new changes When "live update" is off and new changes are detected, show a link to load and prepend the changes to the list. Also adding a line between old and new changes when grouping by pages is off. Bug: T163426 Change-Id: I6a111d23956bdc04caa4c71e9deede056779aafa --- diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php index 5aa693ddd9..2182c6cb7e 100644 --- a/includes/changes/ChangesList.php +++ b/includes/changes/ChangesList.php @@ -747,20 +747,22 @@ class ChangesList extends ContextSource { * @return string[] attribute name => value */ protected function getDataAttributes( RecentChange $rc ) { + $attrs = []; + $type = $rc->getAttribute( 'rc_source' ); switch ( $type ) { case RecentChange::SRC_EDIT: case RecentChange::SRC_NEW: - return [ - 'data-mw-revid' => $rc->mAttribs['rc_this_oldid'], - ]; + $attrs[ 'data-mw-revid' ] = $rc->mAttribs['rc_this_oldid']; + break; case RecentChange::SRC_LOG: - return [ - 'data-mw-logid' => $rc->mAttribs['rc_logid'], - 'data-mw-logaction' => $rc->mAttribs['rc_log_type'] . '/' . $rc->mAttribs['rc_log_action'], - ]; - default: - return []; + $attrs[ 'data-mw-logid' ] = $rc->mAttribs['rc_logid']; + $attrs[ 'data-mw-logaction' ] = $rc->mAttribs['rc_log_type'] . '/' . $rc->mAttribs['rc_log_action']; + break; } + + $attrs[ 'data-mw-ts' ] = $rc->getAttribute( 'rc_timestamp' ); + + return $attrs; } } diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index f9052ad765..8d00e903df 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -973,15 +973,20 @@ class SpecialRecentChanges extends ChangesListSpecialPage { $resetLink = $this->makeOptionsLink( $this->msg( 'rclistfromreset' ), [ 'from' => '' ], $nondefaults ); - $note .= $this->msg( 'rcnotefrom' ) + $noteFromMsg = $this->msg( 'rcnotefrom' ) ->numParams( $options['limit'] ) ->params( $lang->userTimeAndDate( $options['from'], $user ), $lang->userDate( $options['from'], $user ), $lang->userTime( $options['from'], $user ) ) - ->numParams( $numRows ) - ->parse() . ' ' . + ->numParams( $numRows ); + $note .= Html::rawElement( + 'span', + [ 'class' => 'rcnotefrom' ], + $noteFromMsg->parse() + ) . + ' ' . Html::rawElement( 'span', [ 'class' => 'rcoptions-listfromreset' ], diff --git a/languages/i18n/en.json b/languages/i18n/en.json index b2b4179ca0..8c295b289d 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -1375,6 +1375,8 @@ "rcfilters-savedqueries-add-new-title": "Save current filter settings", "rcfilters-restore-default-filters": "Restore default filters", "rcfilters-clear-all-filters": "Clear all filters", + "rcfilters-show-new-changes": "Show new changes", + "rcfilters-previous-changes-label": "Previously viewed changes", "rcfilters-search-placeholder": "Filter recent changes (browse or start typing)", "rcfilters-invalid-filter": "Invalid filter", "rcfilters-empty-filter": "No active filters. All contributions are shown.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index f2755d1602..89f6f414ba 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -1565,6 +1565,8 @@ "rcfilters-savedqueries-add-new-title": "Title for the popup to add new quick link in [[Special:RecentChanges]]. This is for a small popup, please try to use a short string.", "rcfilters-restore-default-filters": "Label for the button that resets filters to defaults", "rcfilters-clear-all-filters": "Title for the button that clears all filters", + "rcfilters-show-new-changes": "Label for the button to show new changes.", + "rcfilters-previous-changes-label": "Label to indicate the changes below have been previously viewed.", "rcfilters-search-placeholder": "Placeholder for the filter search input.", "rcfilters-invalid-filter": "A label for an invalid filter.", "rcfilters-empty-filter": "Placeholder for the filter list when no filters were chosen.", diff --git a/resources/Resources.php b/resources/Resources.php index e3d8a1b241..82f285ea4e 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1857,6 +1857,8 @@ return [ 'rcfilters-savedqueries-cancel-label', 'rcfilters-restore-default-filters', 'rcfilters-clear-all-filters', + 'rcfilters-show-new-changes', + 'rcfilters-previous-changes-label', 'rcfilters-search-placeholder', 'rcfilters-invalid-filter', 'rcfilters-empty-filter', diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js index 49c0b82ca8..34ed2ebef2 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js @@ -11,6 +11,9 @@ OO.EventEmitter.call( this ); this.valid = true; + this.newChangesExist = false; + this.nextFrom = null; + this.liveUpdate = false; }; /* Initialization */ @@ -27,10 +30,26 @@ /** * @event update - * @param {jQuery|string} changesListContent - * @param {jQuery} $fieldset + * @param {jQuery|string} $changesListContent List of changes + * @param {jQuery} $fieldset Server-generated form + * @param {boolean} isInitialDOM Whether the previous dom variables are from the initial page load + * @param {boolean} fromLiveUpdate These are new changes fetched via Live Update + * + * The list of changes has been updated + */ + + /** + * @event newChangesExist + * @param {boolean} newChangesExist + * + * The existence of changes newer than those currently displayed has changed. + */ + + /** + * @event liveUpdateChange + * @param {boolean} enable * - * The list of change is now up to date + * The state of the 'live update' feature has changed. */ /* Methods */ @@ -53,10 +72,70 @@ * @param {jQuery|string} changesListContent * @param {jQuery} $fieldset * @param {boolean} [isInitialDOM] Using the initial (already attached) DOM elements + * @param {boolean} [fromLiveUpdate] These are new changes fetched via Live Update + * @fires update */ - mw.rcfilters.dm.ChangesListViewModel.prototype.update = function ( changesListContent, $fieldset, isInitialDOM ) { + mw.rcfilters.dm.ChangesListViewModel.prototype.update = function ( changesListContent, $fieldset, isInitialDOM, fromLiveUpdate ) { + var from = this.nextFrom; this.valid = true; - this.emit( 'update', changesListContent, $fieldset, isInitialDOM ); + this.extractNextFrom( $fieldset ); + this.emit( 'update', changesListContent, $fieldset, isInitialDOM, fromLiveUpdate ? from : null ); + }; + + /** + * Specify whether new changes exist + * + * @param {boolean} newChangesExist + * @fires newChangesExist + */ + mw.rcfilters.dm.ChangesListViewModel.prototype.setNewChangesExist = function ( newChangesExist ) { + if ( newChangesExist !== this.newChangesExist ) { + this.newChangesExist = newChangesExist; + this.emit( 'newChangesExist', newChangesExist ); + } + }; + + /** + * @return {boolean} Whether new changes exist + */ + mw.rcfilters.dm.ChangesListViewModel.prototype.getNewChangesExist = function () { + return this.newChangesExist; + }; + + /** + * Extract the value of the 'from' parameter from a link in the field set + * + * @param {jQuery} $fieldset + */ + mw.rcfilters.dm.ChangesListViewModel.prototype.extractNextFrom = function ( $fieldset ) { + this.nextFrom = $fieldset.find( '.rclistfrom > a' ).data( 'params' ).from; + }; + + /** + * @return {string} The 'from' parameter that can be used to query new changes + */ + mw.rcfilters.dm.ChangesListViewModel.prototype.getNextFrom = function () { + return this.nextFrom; + }; + + /** + * Toggle the 'live update' feature on/off + * + * @param {boolean} enable + */ + mw.rcfilters.dm.ChangesListViewModel.prototype.toggleLiveUpdate = function ( enable ) { + enable = enable === undefined ? !this.liveUpdate : enable; + if ( enable !== this.liveUpdate ) { + this.liveUpdate = enable; + this.emit( 'liveUpdateChange', this.liveUpdate ); + } + }; + + /** + * @return {boolean} The 'live update' feature is enabled + */ + mw.rcfilters.dm.ChangesListViewModel.prototype.getLiveUpdate = function () { + return this.liveUpdate; }; }( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js index 73ff165a78..a65a9c21ad 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js @@ -13,7 +13,7 @@ this.filtersModel = filtersModel; this.changesListModel = changesListModel; this.savedQueriesModel = savedQueriesModel; - this.requestCounter = 0; + this.requestCounter = {}; this.baseFilterState = {}; this.uriProcessor = null; this.initializing = false; @@ -223,6 +223,8 @@ this.initializing = false; this.switchView( 'default' ); + + this._scheduleLiveUpdate(); }; /** @@ -423,11 +425,9 @@ * @param {boolean} enable True to enable, false to disable */ mw.rcfilters.Controller.prototype.toggleLiveUpdate = function ( enable ) { - if ( enable && !this.liveUpdateTimeout ) { - this._scheduleLiveUpdate(); - } else if ( !enable && this.liveUpdateTimeout ) { - clearTimeout( this.liveUpdateTimeout ); - this.liveUpdateTimeout = null; + this.changesListModel.toggleLiveUpdate( enable ); + if ( this.changesListModel.getLiveUpdate() && this.changesListModel.getNewChangesExist() ) { + this.showNewChanges(); } }; @@ -436,7 +436,7 @@ * @private */ mw.rcfilters.Controller.prototype._scheduleLiveUpdate = function () { - this.liveUpdateTimeout = setTimeout( this._doLiveUpdate.bind( this ), 3000 ); + setTimeout( this._doLiveUpdate.bind( this ), 3000 ); }; /** @@ -444,14 +444,71 @@ * @private */ mw.rcfilters.Controller.prototype._doLiveUpdate = function () { - var controller = this; - this.updateChangesList( {}, true ) - .always( function () { - if ( controller.liveUpdateTimeout ) { - // Live update was not disabled in the meantime - controller._scheduleLiveUpdate(); + if ( !this._shouldCheckForNewChanges() ) { + // skip this turn and check back later + this._scheduleLiveUpdate(); + return; + } + + this._checkForNewChanges() + .then( function ( data ) { + if ( !this._shouldCheckForNewChanges() ) { + // by the time the response is received, + // it may not be appropriate anymore + return; } - } ); + + if ( data.changes !== 'NO_RESULTS' ) { + if ( this.changesListModel.getLiveUpdate() ) { + return this.updateChangesList( false, null, true, false ); + } else { + this.changesListModel.setNewChangesExist( true ); + } + } + }.bind( this ) ) + .always( this._scheduleLiveUpdate.bind( this ) ); + }; + + /** + * @return {boolean} It's appropriate to check for new changes now + * @private + */ + mw.rcfilters.Controller.prototype._shouldCheckForNewChanges = function () { + var liveUpdateFeatureFlag = mw.config.get( 'wgStructuredChangeFiltersEnableLiveUpdate' ) || + new mw.Uri().query.liveupdate; + + return !document.hidden && + !this.changesListModel.getNewChangesExist() && + !this.updatingChangesList && + liveUpdateFeatureFlag; + }; + + /** + * 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 + * + * @private + */ + mw.rcfilters.Controller.prototype._checkForNewChanges = function () { + return this._fetchChangesList( + 'liveUpdate', + { + limit: 1, + from: this.changesListModel.getNextFrom() + } + ); + }; + + /** + * Show the new changes + * + * @return {jQuery.Promise} Promise object that resolves after + * fetching and showing the new changes + */ + mw.rcfilters.Controller.prototype.showNewChanges = function () { + return this.updateChangesList( false, null, true, true ); }; /** @@ -823,25 +880,36 @@ /** * 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] Don't update the URL or invalidate the changes list + * @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) * @return {jQuery.Promise} Promise that is resolved when the update is complete */ - mw.rcfilters.Controller.prototype.updateChangesList = function ( params, isLiveUpdate ) { - if ( !isLiveUpdate ) { + mw.rcfilters.Controller.prototype.updateChangesList = function ( updateUrl, params, isLiveUpdate, invalidateCurrentChanges ) { + updateUrl = updateUrl === undefined ? true : updateUrl; + invalidateCurrentChanges = invalidateCurrentChanges === undefined ? true : invalidateCurrentChanges; + if ( updateUrl ) { this._updateURL( params ); + } + if ( invalidateCurrentChanges ) { this.changesListModel.invalidate(); } + this.changesListModel.setNewChangesExist( false ); + this.updatingChangesList = true; return this._fetchChangesList() .then( // Success function ( pieces ) { var $changesListContent = pieces.changes, $fieldset = pieces.fieldset; - this.changesListModel.update( $changesListContent, $fieldset ); + this.changesListModel.update( $changesListContent, $fieldset, false, isLiveUpdate ); }.bind( this ) // Do nothing for failure - ); + ) + .always( function () { + this.updatingChangesList = false; + }.bind( this ) ); }; /** @@ -935,16 +1003,29 @@ /** * Fetch the list of changes from the server for the current filters * + * @param {string} [counterId='updateChangesList'] 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. */ - mw.rcfilters.Controller.prototype._fetchChangesList = function () { + mw.rcfilters.Controller.prototype._fetchChangesList = function ( counterId, params ) { var uri = this._getUpdatedUri(), stickyParams = this.filtersModel.getStickyParams(), - requestId = ++this.requestCounter, - latestRequest = function () { - return requestId === this.requestCounter; - }.bind( this ); + requestId, + latestRequest; + + counterId = counterId || 'updateChangesList'; + params = params || {}; + + uri.extend( params ); + + this.requestCounter[ counterId ] = this.requestCounter[ counterId ] || 0; + requestId = ++this.requestCounter[ counterId ]; + latestRequest = function () { + return requestId === this.requestCounter[ counterId ]; + }.bind( this ); // Sticky parameters override the URL params // this is to make sure that whether we represent diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js index 12a83cda09..701e61d1b2 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js @@ -19,13 +19,13 @@ $overlay = $( '
' ) .addClass( 'mw-rcfilters-ui-overlay' ), filtersWidget = new mw.rcfilters.ui.FilterWrapperWidget( - controller, filtersModel, savedQueriesModel, { $overlay: $overlay } ); + controller, filtersModel, savedQueriesModel, changesListModel, { $overlay: $overlay } ); // TODO: The changesListWrapperWidget should be able to initialize // after the model is ready. // eslint-disable-next-line no-new new mw.rcfilters.ui.ChangesListWrapperWidget( - filtersModel, changesListModel, $( '.mw-changeslist, .mw-changeslist-empty' ) ); + filtersModel, changesListModel, controller, $( '.mw-changeslist, .mw-changeslist-empty' ) ); controller.initialize( mw.config.get( 'wgStructuredChangeFilters' ), diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less index 0d12b811f3..7f0d34ec93 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less @@ -2,11 +2,6 @@ .client-js { .rcoptions { border: 0; - border-bottom: 1px solid #a2a9b1; - - legend { - display: none; - } } .mw-recentchanges-toplinks { @@ -29,7 +24,7 @@ } .rcfilters-head { - min-height: 310px; + min-height: 220px; margin-top: 1em; &:not( .mw-rcfilters-ui-ready ) { diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less index 89acdc017b..d60e616f41 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less @@ -1,6 +1,26 @@ @import 'mw.rcfilters.mixins'; .mw-rcfilters-ui-changesListWrapperWidget { + + &-newChanges { + min-height: 34px; + margin: 10px 0; + text-align: center; + } + + &-previousChangesIndicator { + margin: 10px 0; + color: #36c; + border-top: 2px solid #36c; + text-align: center; + + &:hover { + color: #72777d; + border-top-color: #72777d; + cursor: pointer; + } + } + &-results { width: 35em; margin: 5em auto; diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js index c2533df818..d571774872 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js @@ -7,12 +7,14 @@ * @constructor * @param {mw.rcfilters.dm.FiltersViewModel} filtersViewModel View model * @param {mw.rcfilters.dm.ChangesListViewModel} changesListViewModel View model + * @param {mw.rcfilters.Controller} controller * @param {jQuery} $changesListRoot Root element of the changes list to attach to - * @param {Object} config Configuration object + * @param {Object} [config] Configuration object */ mw.rcfilters.ui.ChangesListWrapperWidget = function MwRcfiltersUiChangesListWrapperWidget( filtersViewModel, changesListViewModel, + controller, $changesListRoot, config ) { @@ -25,6 +27,7 @@ this.filtersViewModel = filtersViewModel; this.changesListViewModel = changesListViewModel; + this.controller = controller; // Events this.filtersViewModel.connect( this, { @@ -33,7 +36,8 @@ } ); this.changesListViewModel.connect( this, { invalidate: 'onModelInvalidate', - update: 'onModelUpdate' + update: 'onModelUpdate', + newChangesExist: 'onNewChangesExist' } ); this.$element @@ -43,6 +47,8 @@ // Set up highlight containers this.setupHighlightContainers( this.$element ); + + this.setupNewChangesButtonContainer( this.$element ); }; /* Initialization */ @@ -86,16 +92,21 @@ * @param {jQuery|string} $changesListContent The content of the updated changes list * @param {jQuery} $fieldset The content of the updated fieldset * @param {boolean} isInitialDOM Whether $changesListContent is the existing (already attached) DOM + * @param {boolean} from Timestamp of the new changes */ - mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onModelUpdate = function ( $changesListContent, $fieldset, isInitialDOM ) { + mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onModelUpdate = function ( + $changesListContent, $fieldset, isInitialDOM, from + ) { var conflictItem, $message = $( '
' ) .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-results' ), - isEmpty = $changesListContent === 'NO_RESULTS'; + isEmpty = $changesListContent === 'NO_RESULTS', + $lastSeen, + $indicator, + $newChanges = $( [] ); this.$element.toggleClass( 'mw-changeslist', !isEmpty ); if ( isEmpty ) { - this.$changesListContent = null; this.$element.empty(); if ( this.filtersViewModel.hasConflict() ) { @@ -121,10 +132,41 @@ this.$element.append( $message ); } else { - this.$changesListContent = $changesListContent; if ( !isInitialDOM ) { - this.$element.empty().append( this.$changesListContent ); + this.$element.empty().append( $changesListContent ); + + if ( from ) { + $lastSeen = null; + this.$element.find( 'li[data-mw-ts]' ).each( function () { + var $li = $( this ), + ts = $li.data( 'mw-ts' ); + + if ( ts >= from ) { + $newChanges = $newChanges.add( $li ); + } else if ( $lastSeen === null ) { + $lastSeen = $li; + return false; + } + } ); + + if ( $lastSeen ) { + $indicator = $( '
' ) + .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-previousChangesIndicator' ) + .text( mw.message( 'rcfilters-previous-changes-label' ).text() ); + + $indicator.on( 'click', function () { + $indicator.detach(); + } ); + + $lastSeen.before( $indicator ); + } + + $newChanges + .hide() + .fadeIn( 1000 ); + } } + // Set up highlight containers this.setupHighlightContainers( this.$element ); @@ -141,6 +183,43 @@ this.$element.addClass( 'mw-rcfilters-ui-ready' ); }; + /** + * Respond to changes list model newChangesExist + * + * @param {boolean} newChangesExist Whether new changes exist + */ + mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onNewChangesExist = function ( newChangesExist ) { + this.showNewChangesLink.toggle( newChangesExist ); + }; + + /** + * Respond to the user clicking the 'show new changes' button + */ + mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onShowNewChangesClick = function () { + this.controller.showNewChanges(); + }; + + /** + * Setup the container for the 'new changes' button. + * + * @param {jQuery} $content + */ + mw.rcfilters.ui.ChangesListWrapperWidget.prototype.setupNewChangesButtonContainer = function ( $content ) { + this.showNewChangesLink = new OO.ui.ButtonWidget( { + framed: false, + label: mw.message( 'rcfilters-show-new-changes' ).text(), + flags: [ 'progressive' ] + } ); + this.showNewChangesLink.connect( this, { click: 'onShowNewChangesClick' } ); + this.showNewChangesLink.toggle( false ); + + $content.before( + $( '
' ) + .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-newChanges' ) + .append( this.showNewChangesLink.$element ) + ); + }; + /** * Set up the highlight containers with all color circle indicators. * @@ -148,8 +227,9 @@ */ mw.rcfilters.ui.ChangesListWrapperWidget.prototype.setupHighlightContainers = function ( $content ) { var uri = new mw.Uri(), + highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights', $highlights = $( '
' ) - .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights' ) + .addClass( highlightClass ) .append( $( '
' ) .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-color-none' ) diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js index a6b363d168..d3cbc20c6e 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js @@ -9,11 +9,14 @@ * @param {mw.rcfilters.Controller} controller Controller * @param {mw.rcfilters.dm.FiltersViewModel} model View model * @param {mw.rcfilters.dm.SavedQueriesModel} savedQueriesModel Saved queries model + * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel * @param {Object} [config] Configuration object * @cfg {Object} [filters] A definition of the filter groups in this list * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups */ - mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( controller, model, savedQueriesModel, config ) { + mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( + controller, model, savedQueriesModel, changesListModel, config + ) { var $top, $topRow, $bottom; config = config || {}; @@ -35,7 +38,8 @@ ); this.liveUpdateButton = new mw.rcfilters.ui.LiveUpdateButtonWidget( - this.controller + this.controller, + changesListModel ); this.numChangesWidget = new mw.rcfilters.ui.ChangesLimitButtonWidget( diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js index 50e36372f1..44f6da74b5 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js @@ -54,7 +54,7 @@ * @return {boolean} false */ mw.rcfilters.ui.FormWrapperWidget.prototype.onLinkClick = function ( e ) { - this.controller.updateChangesList( $( e.target ).data( 'params' ) ); + this.controller.updateChangesList( true, $( e.target ).data( 'params' ) ); return false; }; @@ -78,7 +78,7 @@ data[ $( this ).prop( 'name' ) ] = value; } ); - this.controller.updateChangesList( data ); + this.controller.updateChangesList( true, data ); return false; }; @@ -143,11 +143,20 @@ this.$element.find( '.mw-recentchanges-table' ).detach(); this.$element.find( 'hr' ).detach(); } + if ( !this.$element.find( '.rcshowhide' ).contents().length ) { this.$element.find( '.rcshowhide' ).detach(); // If we're hiding rcshowhide, the '
's are around it, // there's no need for them either. this.$element.find( 'br' ).detach(); } + + this.$element.find( + 'legend, .rclistfrom, .rcnotefrom, .rcoptions-listfromreset' + ).detach(); + + if ( this.$element.text().trim() === '' ) { + this.$element.detach(); + } }; }( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js index 90ee4d7dfb..67c113d751 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js @@ -6,9 +6,10 @@ * * @constructor * @param {mw.rcfilters.Controller} controller - * @param {Object} config Configuration object + * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel + * @param {Object} [config] Configuration object */ - mw.rcfilters.ui.LiveUpdateButtonWidget = function MwRcfiltersUiLiveUpdateButtonWidget( controller, config ) { + mw.rcfilters.ui.LiveUpdateButtonWidget = function MwRcfiltersUiLiveUpdateButtonWidget( controller, changesListModel, config ) { config = config || {}; // Parent @@ -18,9 +19,11 @@ }, config ) ); this.controller = controller; + this.model = changesListModel; // Events - this.connect( this, { change: 'onChange' } ); + this.connect( this, { click: 'onClick' } ); + this.model.connect( this, { liveUpdateChange: 'onLiveUpdateChange' } ); this.$element.addClass( 'mw-rcfilters-ui-liveUpdateButtonWidget' ); }; @@ -32,11 +35,20 @@ /* Methods */ /** - * Respond to the button being toggled. - * @param {boolean} enable Whether the button is now pressed/enabled + * Respond to the button being clicked */ - mw.rcfilters.ui.LiveUpdateButtonWidget.prototype.onChange = function ( enable ) { - this.controller.toggleLiveUpdate( enable ); + mw.rcfilters.ui.LiveUpdateButtonWidget.prototype.onClick = function () { + this.controller.toggleLiveUpdate(); + }; + + /** + * Respond to the 'live update' feature being turned on/off + * + * @param {boolean} enable Whether the 'live update' feature is now on/off + */ + mw.rcfilters.ui.LiveUpdateButtonWidget.prototype.onLiveUpdateChange = function ( enable ) { + this.setValue( enable ); + this.setIcon( enable ? 'stop' : 'play' ); }; }( mediaWiki ) ); diff --git a/tests/phpunit/includes/changes/EnhancedChangesListTest.php b/tests/phpunit/includes/changes/EnhancedChangesListTest.php index 28818d9b47..465bc22f60 100644 --- a/tests/phpunit/includes/changes/EnhancedChangesListTest.php +++ b/tests/phpunit/includes/changes/EnhancedChangesListTest.php @@ -99,7 +99,7 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { $enhancedChangesList->recentChangesLine( $recentChange, false ); $html = $enhancedChangesList->endRecentChangesList(); - $this->assertRegExp( '/data-mw-revid="5" class="[^"]*mw-enhanced-rc[^"]*"/', $html ); + $this->assertRegExp( '/data-mw-revid="5" data-mw-ts="20131103092153" class="[^"]*mw-enhanced-rc[^"]*"/', $html ); $recentChange2 = $this->getEditChange( '20131103092253' ); $enhancedChangesList->recentChangesLine( $recentChange2, false ); @@ -133,7 +133,7 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase { private function getEditChange( $timestamp ) { $user = $this->getMutableTestUser()->getUser(); $recentChange = $this->testRecentChangesHelper->makeEditRecentChange( - $user, 'Cat', $timestamp, 5, 191, 190, 0, 0 + $user, 'Cat', 0, 5, 191, $timestamp, 0, 0 ); return $recentChange; diff --git a/tests/phpunit/includes/changes/OldChangesListTest.php b/tests/phpunit/includes/changes/OldChangesListTest.php index f892eb70ed..90c60c826c 100644 --- a/tests/phpunit/includes/changes/OldChangesListTest.php +++ b/tests/phpunit/includes/changes/OldChangesListTest.php @@ -126,9 +126,9 @@ class OldChangesListTest extends MediaWikiLangTestCase { $oldChangesList = $this->getOldChangesList(); $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); - $this->assertRegExp( '/
  • /', + $this->assertRegExp( '/
  • /', $line ); - $this->assertRegExp( '/
  • /', + $this->assertRegExp( '/
  • /', $line ); }