* @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;
}
}
$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' ],
"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.",
"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.",
'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',
OO.EventEmitter.call( this );
this.valid = true;
+ this.newChangesExist = false;
+ this.nextFrom = null;
+ this.liveUpdate = false;
};
/* Initialization */
/**
* @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 */
* @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 ) );
this.filtersModel = filtersModel;
this.changesListModel = changesListModel;
this.savedQueriesModel = savedQueriesModel;
- this.requestCounter = 0;
+ this.requestCounter = {};
this.baseFilterState = {};
this.uriProcessor = null;
this.initializing = false;
this.initializing = false;
this.switchView( 'default' );
+
+ this._scheduleLiveUpdate();
};
/**
* @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();
}
};
* @private
*/
mw.rcfilters.Controller.prototype._scheduleLiveUpdate = function () {
- this.liveUpdateTimeout = setTimeout( this._doLiveUpdate.bind( this ), 3000 );
+ setTimeout( this._doLiveUpdate.bind( this ), 3000 );
};
/**
* @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 );
};
/**
/**
* 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 ) );
};
/**
/**
* 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
$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' ),
.client-js {
.rcoptions {
border: 0;
- border-bottom: 1px solid #a2a9b1;
-
- legend {
- display: none;
- }
}
.mw-recentchanges-toplinks {
}
.rcfilters-head {
- min-height: 310px;
+ min-height: 220px;
margin-top: 1em;
&:not( .mw-rcfilters-ui-ready ) {
@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;
* @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
) {
this.filtersViewModel = filtersViewModel;
this.changesListViewModel = changesListViewModel;
+ this.controller = controller;
// Events
this.filtersViewModel.connect( this, {
} );
this.changesListViewModel.connect( this, {
invalidate: 'onModelInvalidate',
- update: 'onModelUpdate'
+ update: 'onModelUpdate',
+ newChangesExist: 'onNewChangesExist'
} );
this.$element
// Set up highlight containers
this.setupHighlightContainers( this.$element );
+
+ this.setupNewChangesButtonContainer( this.$element );
};
/* Initialization */
* @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() ) {
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 );
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.
*
*/
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' )
* @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 || {};
);
this.liveUpdateButton = new mw.rcfilters.ui.LiveUpdateButtonWidget(
- this.controller
+ this.controller,
+ changesListModel
);
this.numChangesWidget = new mw.rcfilters.ui.ChangesLimitButtonWidget(
* @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;
};
data[ $( this ).prop( 'name' ) ] = value;
} );
- this.controller.updateChangesList( data );
+ this.controller.updateChangesList( true, data );
return false;
};
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 ) );
*
* @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
}, 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' );
};
/* 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 ) );
$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 );
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;
$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 );
}