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
This commit is contained in:
Stephane Bisson 2017-07-21 11:41:36 -04:00
parent 19cc066749
commit e2bea6350c
16 changed files with 365 additions and 72 deletions

View file

@ -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;
}
}

View file

@ -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' ],

View file

@ -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.",

View file

@ -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.",

View file

@ -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',

View file

@ -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 change is now up to date
* 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 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 ) );

View file

@ -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

View file

@ -19,13 +19,13 @@
$overlay = $( '<div>' )
.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' ),

View file

@ -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 ) {

View file

@ -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;

View file

@ -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 = $( '<div>' )
.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 = $( '<div>' )
.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(
$( '<div>' )
.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 = $( '<div>' )
.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights' )
.addClass( highlightClass )
.append(
$( '<div>' )
.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-color-none' )

View file

@ -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(

View file

@ -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 '<br>'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 ) );

View file

@ -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 ) );

View file

@ -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;

View file

@ -126,9 +126,9 @@ class OldChangesListTest extends MediaWikiLangTestCase {
$oldChangesList = $this->getOldChangesList();
$line = $oldChangesList->recentChangesLine( $recentChange, false, 1 );
$this->assertRegExp( '/<li data-mw-revid="\d+" class="[\w\s-]*mw-tag-vandalism[\w\s-]*">/',
$this->assertRegExp( '/<li data-mw-revid="\d+" data-mw-ts="\d+" class="[\w\s-]*mw-tag-vandalism[\w\s-]*">/',
$line );
$this->assertRegExp( '/<li data-mw-revid="\d+" class="[\w\s-]*mw-tag-newbie[\w\s-]*">/',
$this->assertRegExp( '/<li data-mw-revid="\d+" data-mw-ts="\d+" class="[\w\s-]*mw-tag-newbie[\w\s-]*">/',
$line );
}