Jump to content

User:NatigKrolik/Rollback.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/** <nowiki>
 * Rollback.js by [[User:NatigKrolik]]
 * --------------------experimental--------------------
 * provides the 'rollback' link to non-rollbackers
 * THIS SOFTWARE IS PROVIDED "AS IS", YOU OWN ANY RESPONSIBILITY WHILE USING IT
 * !!! not intended for use by vandals !!!
 */
/* global $, mw */
$( document ).ready( function () {
	'use strict';

	var api, $undoLink, $ntitle, specialPage, userGroups,
		SHOW_ROLLBACK_EDIT_COUNT, modules, diffOnRb, botMode;

	$undoLink = $( 'ul#pagehistory li:first-child span.mw-history-undo' );
	$ntitle = $( '#mw-diff-ntitle2' );
	specialPage = mw.config.get( 'wgCanonicalSpecialPageName' );
	if (
		$undoLink.length === 0 &&
		( $ntitle.length === 0 || $( '#differences-nextlink' ).length > 0 ) &&
		[
			'Contributions',
			'Watchlist',
			'Recentchanges',
			'Recentchangeslinked'
		].indexOf( specialPage ) === -1
	) {
		return;// no [rollback] button to insert
	}
	// TODO: auto-patrol reverted changes

	userGroups = mw.config.get( 'wgUserGroups' );
	if ( userGroups.indexOf( 'rollbacker' ) !== -1 || userGroups.indexOf( 'sysop' ) !== -1 ) {
		return;// user is rollbacker, no need for these buttons
	}

	// https://www.mediawiki.org/wiki/Manual:$wgShowRollbackEditCount
	SHOW_ROLLBACK_EDIT_COUNT = 10;

	modules = [ 'mediawiki.api' ];
	diffOnRb = ( mw.user.options.get( 'norollbackdiff' ) !== 1 );
	if ( diffOnRb ) {
		modules.push( 'mediawiki.action.history.diff' );
	}
	botMode = mw.util.getParamValue( 'bot' );

	function loadMessages( messages ) {
		return api.get( {
			action: 'query',
			formatversion: 2,
			meta: 'allmessages',
			amlang: mw.config.get( 'wgUserLanguage' ),
			ammessages: messages
		} )
		.done( function ( data ) {
			$.each( data.query.allmessages, function ( i, e ) {
				mw.messages.set( e.name, e.content );
			} );
		} );
	}

	function showLatestDiff( pageTitle ) {
		api.get( {
			action: 'query',
			formatversion: 2,
			prop: 'revisions',
			titles: pageTitle,
			rvdiffto: 'prev',
			rvlimit: 1
		} ).done( function ( rev ) {
			$( '#mw-content-text' ).append(
				'<table class="diff">' +
				Array( 3 ).join( '<col class="diff-marker"/><col class="diff-content"/>' ) +
				rev.query.pages[ 0 ].revisions[ 0 ].diff.body +
				'</table>'
			);
		} );
	}

	function pushHistory( url ) {
		window.history.pushState( { path: window.location.href }, '', url );
	}

	function onGotRevisions( rbl, pageTitle, lastEditor, undoRevid, data ) {
		var page, revision, rbSummary, rbSuccess, rbPostConfig;
		$.each( data.query.allmessages, function ( i, e ) {
			mw.messages.set( e.name, e.content );
		} );
		page = data.query.pages[ 0 ];
		if ( page.revisions === undefined || page.revisions.length === 0 ) {
			// the last editor of the page is its single one
			pushHistory( rbl.href );
			$( '#firstHeading' ).text( mw.message( 'rollbackfailed' ).text() );
			$( '#p-views ul>li' ).removeClass( 'selected' );
			$( '#contentSub' ).empty();
			$( '#mw-content-text' ).text( mw.message( 'cantrollback' ).text() );
			return;
		}
		revision = page.revisions[ 0 ];
		rbSummary = mw.message( 'revertpage', revision.user, lastEditor ).text();
		rbSuccess = mw.message( 'rollback-success', lastEditor, revision.user ).text();
		rbPostConfig = {
			action: 'edit',
			undo: undoRevid,
			undoafter: revision.revid,
			title: pageTitle,
			summary: rbSummary,
			minor: 1
		};
		if ( botMode !== null ) {
			rbPostConfig.bot = botMode;
		}
		api.postWithToken( 'csrf', rbPostConfig )
		.done( function () {
			pushHistory( rbl.href );
			$( '#p-views ul>li' ).removeClass( 'selected' );
			$( '#contentSub' ).empty();
			$( '#firstHeading' ).text( mw.message( 'actioncomplete' ).text() );
			$( '#mw-content-text' ).empty().append(
				$( '<p>' ).text( rbSuccess )
			)
			.append(
				$( '<p>' ).attr( 'id', 'mw-returnto' ).html(
					mw.message( 'returnto',
						'<a href="' + mw.util.getUrl( pageTitle ) +
						'" title="' + pageTitle.replace( /_/g, ' ' ) + '">' +
						pageTitle.replace( /_/g, ' ' ) + '</a>'
					).text()
				)
			);
			if ( diffOnRb ) {
				showLatestDiff( pageTitle );
			}
		} )
		.fail( function ( code, data ) {
			pushHistory( rbl.href );
			$( '#firstHeading' ).text( mw.message( 'rollbackfailed' ).text() );
			$( '#mw-content-text' ).text( data && data.error && data.error.info ? data.error.info : '' );
		} );
	}

	function onRbLinkClick( rbl, pageTitle, lastEditor, undoRevid ) {
		api.get( {
			// require the revision id and the user name
			// of the last revision not made by the last user
			action: 'query',
			formatversion: 2,
			prop: 'revisions',
			rvlimit: 1,
			titles: pageTitle,
			rvexcludeuser: lastEditor,
			rvprop: 'ids|user',
			meta: 'allmessages',
			amlang: mw.config.get( 'wgContentLanguage' ),
			ammessages: 'revertpage'
		} )
		.done( onGotRevisions.bind( null, rbl, pageTitle, lastEditor, undoRevid ) );
	}

	function getRbLink( pageTitle, lastEditor, undoRevid, rbLinkHrefConfig, rbLinkText ) {
		return $( '<a>' )
		.attr( {
			href: mw.util.wikiScript() + '?' + $.param( rbLinkHrefConfig ),
			title: mw.message( 'tooltip-rollback' ).text()
		} )
		.text( rbLinkText )
		.click( function ( event ) {
			event.preventDefault();
			onRbLinkClick( this, pageTitle, lastEditor, undoRevid );
		} )
		.appendTo( $( '<span>' ).addClass( 'mw-rollback-link' ) ).parent();
	}

	function getRbLinkFromRevisions( pageTitle, lastEditor, data ) {
		var page, rbEdits, undoRevid, rbLinkText, rbLinkHrefConfig;
		if (
			data === undefined ||
			data.query === undefined ||
			data.query.pages === undefined ||
			data.query.pages.length !== 1
		) {
			return null;
		}
		page = data.query.pages[ 0 ];
		if ( page.revisions === undefined || page.revisions.length === 0 ) {
			return null;
		}
		rbEdits = null;
		undoRevid = page.revisions[ 0 ].revid;
		$.each( page.revisions, function ( i, rev ) {
			if ( rev.user.replace( / /g, '_' ) !== lastEditor.replace( / /g, '_' ) ) {
				rbEdits = i;
				return false;
			}
		} );
		rbLinkText = mw.message( 'rollbacklink' ).text();
		if ( rbEdits === null ) {
			if ( page.revisions.length === SHOW_ROLLBACK_EDIT_COUNT + 1 ) {
				rbLinkText = mw.message( 'rollbacklinkcount-morethan', SHOW_ROLLBACK_EDIT_COUNT ).text();
			} else {
				return null;
			}
		} else {
			rbLinkText = mw.message( 'rollbacklinkcount', rbEdits ).text();
		}
		rbLinkHrefConfig = {
			title: pageTitle,
			action: 'rollback',
			user: lastEditor,
			token: csrfToken.get( 'editToken' )
		};
		if ( botMode !== null ) {
			rbLinkHrefConfig.bot = botMode;
		}
		return getRbLink( pageTitle, lastEditor, undoRevid, rbLinkHrefConfig, rbLinkText );
	}

	function createRbLink( pageTitle, lastEditor ) {
		return api.get( {
			// get the latest SHOW_ROLLBACK_EDIT_COUNT + 1 editors
			action: 'query',
			formatversion: 2,
			prop: 'revisions',
			rvlimit: SHOW_ROLLBACK_EDIT_COUNT + 1,
			titles: pageTitle,
			rvprop: 'ids|user'
		} )
		.then( function ( data ) {
			var rbl = getRbLinkFromRevisions( pageTitle, lastEditor, data );
			if ( rbl === null ) {
				return $.Deferred().reject();
			}
			return rbl;
		} );
	}

	function getUserFromLink( $link ) {
		if ( $link.hasClass( 'mw-anonuserlink' ) ) {
			// Works also for shortened IPv6 addresses
			return $link.attr( 'title' ).split( '/' )[ 1 ];
		}
		return $link.text();
	}

	function getPageFromLink( $li ) {
		var $diffLink = $li.find( 'a[href*="&diff="]' );
		if ( $diffLink.length === 1 ) {
			return mw.util.getParamValue( 'title', $diffLink.attr( 'href' ) );
		}
		return null;
	}

	function historyMode() {
		var $history = $( 'ul#pagehistory' );
		if ( $history.length === 0 ) {
			return true;
		}
		createRbLink(
			mw.config.get( 'wgPageName' ),
			getUserFromLink( $history.find( 'li:first-child span.history-user a.mw-userlink' ).first() )
		)
		.done( function ( $rbLink ) {
			$rbLink.insertBefore( $undoLink ).after( ' | ' );
		} );
	}

	function diffMode() {
		if ( $ntitle.length === 0 ) {
			return true;
		}
		createRbLink(
			mw.config.get( 'wgPageName' ),
			getUserFromLink( $ntitle.find( 'a.mw-userlink' ).first() )
		)
		.done( function ( $rbLink ) {
			$rbLink.prepend( '[' ).append( ']' ).appendTo( $ntitle ).before( '&nbsp;&nbsp;&nbsp;' );
		} );
	}

	function contributionsMode() {
		var lastEditor;
		if ( specialPage !== 'Contributions' ) {
			return true;
		}
		lastEditor = mw.util.getParamValue( 'target' );
		if ( lastEditor === null ) {
			lastEditor = mw.config.get( 'wgPageName' ).split( '/' )[ 1 ];
		}
		$( '.ns-special ul li' ).each( function () {
			var pageTitle,
				$li = $( this ),
				$uctop = $li.find( '.mw-uctop' );
			if ( $uctop.length === 1 && $li.find( '.newpage' ).length === 0 ) {
				// only top edits, excluding new pages
				pageTitle = getPageFromLink( $li );
				if ( pageTitle === null ) {
					pageTitle = $li.find( '.mw-contributions-title' ).text();
				}
				createRbLink( pageTitle, lastEditor )
				.done( function ( $rbLink ) {
					$rbLink.prepend( ' [' ).append( ']' ).insertAfter( $uctop );
				} );
			}
		} );
	}

	function changesListMode() {
		var seen;
		if ( [ 'Watchlist', 'Recentchanges', 'Recentchangeslinked' ].indexOf( specialPage ) === -1 ) {
			return true;
		}
		seen = Object.create( null );
		$( '.ns-special ul li' ).each( function () {
			var $li = $( this ),
				pageTitle = getPageFromLink( $li );

			if ( pageTitle === null ) {
				pageTitle = $li.find( '.mw-changeslist-title' ).text();
			}

			if ( seen[ pageTitle ] !== undefined ) {
				// not the latest edit to this page
				return;
			}
			seen[ pageTitle ] = true;

			createRbLink( pageTitle, getUserFromLink( $li.find( '.mw-userlink' ) ) )
			.done( function ( $rbLink ) {
				$rbLink.prepend( ' [' ).append( ']' ).appendTo( $li );
			} );
		} );
	}

	function onMessagesLoaded() {
		// The chain stops as soon as one of the functions takes care of adding rollback links
		return historyMode() && diffMode() && contributionsMode() && changesListMode();
	}

	function onModulesLoaded() {
		api = new mw.Api();
		loadMessages( [
			'rollback-success',
			'rollbacklink',
			'rollbacklinkcount',
			'rollbacklinkcount-morethan',
			'action-rollback',
			'tooltip-rollback',
			'cantrollback',
			'actioncomplete',
			'rollbackfailed',
			'returnto'
		] )
		.done( onMessagesLoaded );
	}
	mw.loader.using( modules, onModulesLoaded );
} );
// </nowiki>