MediaWiki:RenameRequest.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
Request renaming of an image
composed in 2012 by Rillke

Thanks to all translators.
 2018-08: translation blocks outsourced (by Perhelion)

@Revision: 20:43, 5 October 2019 (UTC)
**/
// <nowiki>
/* eslint indent:[error,tab,{outerIIFEBody:0}]*/
/* global jQuery:false, mediaWiki:false, AjaxQuickDelete*/

( function ( $, mw ) {
'use strict';

if ( window.rRename || mw.config.get( 'wgNamespaceNumber' ) !== 6 ) {
	return;
}

var RR,
	pn = mw.config.get( 'wgPageName' ),
	lang = mw.config.get( 'wgUserLanguage' ),
	user = mw.config.get( 'wgUserName' ),
	winHeight = $( window ).height(),
	// Please update: real existing subpages, but in fact they are just fallback
	i18n = [ 'ar', 'az', 'be-tarask', 'bn', 'ca', 'cs', 'de', 'es', 'fa', 'fr', 'gl', 'hr', 'id', 'it', 'ja', 'kk', 'ko', 'ml', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sv', 'tr', 'uk', 'yue', 'zh' ];

RR = window.rRename = {

	rInitPolicy: function () {
		var hovertable = $(),
			rInjectRationale = function ( $el, $tr ) {
				var data = $el.data( 'com-v' );
				if ( data && $tr[ 0 ].firstElementChild && !data[ $el[ 0 ].selectedIndex ] ) {
					data[ $el[ 0 ].selectedIndex ] = $tr[ 0 ].firstElementChild.title;
				}
				hovertable.siblings( '.ui-state-focus' ).removeClass( 'ui-state-focus' );
				$tr.addClass( 'ui-state-focus' );
			},
			selectionHandler = function ( e ) {
				var $this = e.target;
				if ( $this.tagName !== 'A' || !$this.href || $this.getAttribute( 'href' )[ 0 ] !== '#' ) {
					e.preventDefault();
				}
				$this = $( this );
				if ( $this.index() ) {
					RR.$selRationale.val( ( $this.data( 'com-owner' ) || $this ).data( 'v' ) );
					rInjectRationale( RR.$selRationale, $this );
					RR.$selRationale.triggerHandler( 'change.conv' );
				}
			},
			skip = 0,
			option = 1,
			$owner,

			rowMap = {};

		this.$selRationale = $( '<select>' ).attr( {
			id: 'selRationale',
			style: 'width: 99%',
			size: 1
		} ).tipsy( {
			gravity: 's',
			fallback: this.rI18n.tRationale,
			trigger: 'focus'
		} ).append( $( '<option>', { value: 0 } ).text( this.rI18n.pRationale ) )/* .val(0)*/
			.on( 'change', function () {
				var val = Number( this.value );
				if ( val ) {
					val = hovertable.eq( val );
					rInjectRationale( $( this ), val );
					RR.rPolicy.animate( { scrollTop: val.offset().top - RR.rPolicy.offset().top + RR.rPolicy.scrollTop() } );
				}
			} );

		mw.util.addCSS( 'table.hovertable tr:hover{background-color:white!important;border:1px solid #ADF!important;outline:1px solid #ADF}table.hovertable tr:hover>td{background-color:white!important;border:1px solid #ADF!important}table.hovertable tr:active>td{background:orange!important}table.hovertable tr:focus>td{background:orange!important}table.hovertable tr.ui-state-focus{outline:1px solid #7E7!important}table.hovertable{cursor:pointer}' );
		hovertable = this.rPolicy
			.css( {
				maxHeight: winHeight - 300,
				overflow: 'auto'
			} )
			.find( 'table' )
			.addClass( 'hovertable' );

		this.rPolicy.data( 'com-rowmap', rowMap );
		hovertable = hovertable.find( 'tr' ).each( function ( i, el ) {
			if ( !i ) {
				return;
			}

			var $el = $( el ),
				$tds = $el.find( 'td' ),
				tdVerbose = $tds.eq( 1 ).find( 'b' ).text();

			$( this ).on( 'click', selectionHandler );
			rowMap[ i ] = option;
			if ( --skip > 0 ) {
				$el.data( 'com-owner', $owner );
				return;
			}
			var rowspan = $tds.eq( 0 ).attr( 'rowspan' );
			if ( rowspan ) {
				skip = Number( rowspan );
			}

			$owner = $el;

			RR.$selRationale.append( $( '<option>', { value: i } ).text( option + '. ' + tdVerbose.substr( 0, 80 ) + ( tdVerbose.length > 70 ? '…' : '' ) ) );
			$el.data( 'v', i );
			option++;
		} );
		this.$selRationaleL = $( '<label>', {
			'for': 'txtReason',
			text: this.rI18n.lRationale
		} );
	},

	rInjectPolicy: function () {
		var $dlgNode = $( '#AjaxDeleteContainer' ),
			reason = $( '#AjaxQuestion1' ).val(),
			regReason = this.rConfig.addReason;

		if ( !this.$injectNode.length ) {
			return;
		}

		if ( !$dlgNode.length ) {
			$dlgNode = this.$injectNode.parent().parent();
		}

		this.rInitPolicy();
		$dlgNode = $dlgNode.parent();
		$dlgNode.dialog( 'option', 'width', Math.max( $dlgNode.dialog( 'option', 'width' ), this.rConfig.dlg.width ) )
			.prepend( this.rPolicy )
			.dialog( 'option', 'position', 'center top' );

		this.$injectNode.after(
			this.$selRationaleL, '<br>',
			this.$selRationale, '<br>'
		);
		this.$injectNode.remove();
		// Convert to RegExp
		regReason = mw.util.escapeRegExp( regReason );
		regReason = this.rConfig.regReason = new RegExp( regReason.replace( /%NUMBER%/g, '(\\d)' ) );
		if ( reason ) {
			if ( regReason.test( reason ) && RegExp.$1 ) {
				var v = Number( RegExp.$1 );
				this.$selRationale.prop( 'selectedIndex', v )
					.data( 'com-v', {} );
			}
			if ( reason.indexOf( this.rConfig.reasonSeparator ) < 0 ) {
				// Maybe better fallback
				if ( /\)( [^\s] )/.test( reason ) && RegExp.$1 ) {
					this.rConfig.reasonSeparator = RegExp.$1;
				} else {
					this.rConfig.reasonSeparator = '';
				}
			}
		} else {
			this.rConfig.reasonSeparator = '';
		}
		this.$selRationale.on( 'change.conv', function () {
			RR.convertRationale( this );
		} );
		this.showProgress();
	},

	convertRationale: function ( $selRationale ) {
		// TODO: id is not for sure
		var $reason = $( '#AjaxQuestion1' ),
			reason = '',
			rationale = '',
			// rationaleTmp = '',
			rationaleNr = 0;
		$selRationale = $( $selRationale );
		if ( !$reason.length || !$selRationale.length ) {
			return;
		}

		if ( ( rationaleNr = Number( $selRationale.val() ) ) ) {
			rationaleNr = this.rPolicy.data( 'com-rowmap' )[ rationaleNr ];
			rationale = this.rConfig.addReason.replace( /%NUMBER%/g, rationaleNr );
			if ( ( reason = $selRationale.data( 'com-v' ) ) ) {
				if ( ( rationaleNr = reason[ rationaleNr ] ) ) {
					rationale += ' (' + rationaleNr + ')';
				}
			}
			reason = $reason.val();
			if ( this.rConfig.reasonSeparator ) {
				reason = reason.split( this.rConfig.reasonSeparator );
				reason = reason[ ( reason[ 1 ] ? 1 : 0 ) ];
			}

			rationaleNr = new RegExp( this.rConfig.regReason.source + '( \\([^()\\n]+\\))?' );
			reason = reason.replace( rationaleNr, '' );
			reason = reason ? this.rConfig.reasonSeparator + reason : '';
			$reason.val( rationale + reason );
		}
	},

	rDialog: function () {
		var dlgButtons = {},
			$submitButton,
			$txtNewNameL = $( '<label>', {
				'for': 'txtNewName',
				text: this.rI18n.lNewName
			} ),
			$txtReasonL = $( '<label>', {
				'for': 'txtReason',
				text: this.rI18n.lReason
			} );

		this.$txtNewName = $( '<input>', {
			id: 'txtNewName',
			type: 'text',
			style: 'width: 99%',
			maxLength: 255,
			placeholder: this.rI18n.pNewName,
			value: pn
		} ).on( 'change', function () {
			var val = RR.cleanFileName( this.value ).replace( /%/g, ' ' );
			if ( val !== this.value ) {
				this.value = val;
			}

		} ).trigger( 'keyup' ).tipsy( {
			gravity: 's',
			fallback: this.rI18n.tNewName,
			trigger: 'focus'
		} );
		this.$txtReason = $( '<input>' ).attr( {
			id: 'txtReason',
			type: 'text',
			style: 'width: 99%',
			placeholder: this.rI18n.pReason
		} ).tipsy( {
			gravity: 's',
			fallback: this.rI18n.tReason,
			trigger: 'focus'
		} );

		this.rInitPolicy();

		[ this.$txtNewName, this.$selRationale, this.$txtReason ].forEach( function ( el ) {
			el.on( 'keyup', function ( e ) {
				$submitButton.submitOnEnter( e );
			} );
		} );

		this.$dlgNode = $( '<div>', { style: 'border:1px solid white' } ).append(
			this.rPolicy,
			$txtNewNameL, '<br>',
			this.$txtNewName, '<br><br>',
			this.$selRationaleL, '<br>',
			this.$selRationale, '<br><br>',
			$txtReasonL, '<br>',
			this.$txtReason
		);

		dlgButtons[ this.rI18n.submitButtonLabel ] = function () {
			RR.tasks = [];
			RR.$dButtons.button( 'option', 'disabled', true );
			RR.addTask( 'rCheckInputs' );
			RR.addTask( 'doesFileExist' );
			RR.fileNameExistsCB = 'rFileExists';
			RR.addTask( 'rIsOnBlackList' );
			RR.addTask( 'rSendRequest' );
			RR.addTask( 'rReady' );
			RR.nextTask();
		};
		dlgButtons[ this.rI18n.cancelButtonLabel ] = function () {
			$( this ).dialog( 'close' );
		};
		this.showProgress();

		this.$dlgNode.dialog( {
			modal: true,
			closeOnEscape: true,
			position: 'center',
			title: this.rConfig.helpLink + this.rI18n.headline,
			height: this.rConfig.dlg.height,
			width: Math.min( $( window ).width(), this.rConfig.dlg.width ),
			buttons: dlgButtons,
			close: function () {
				$( this ).dialog( 'destroy' );
				$( this ).remove();
				$( '.tipsy' ).remove();
				RR.dlgPresent = false;
			},
			open: function () {
				var $dlg = $( this );
				$dlg.parents( '.ui-dialog' ).css( {
					position: 'fixed',
					top: Math.round( ( winHeight - Math.min( winHeight, $( '.ui-dialog.ui-widget' ).height() ) ) / 2 ) + 'px'
				} );
				RR.$dButtons = $dlg.parent().find( '.ui-dialog-buttonpane' ).find( 'button' );
				$submitButton = RR.$dButtons.eq( 0 )
					.specialButton( 'proceed' ).button();
				$submitButton.submitOnEnter = function ( e ) {
						if ( e.which === 13 && !this.button( 'option', 'disabled' ) ) {
							this.trigger( 'click' );
						}

					};
				RR.$dButtons.eq( 1 ).specialButton( 'cancel' );
			},
			create: function () {
				$( this ).dialog( 'option', 'position', 'center' );
			}
		} );

		this.dlgPresent = true;
	},

	rGetPolicy: function () {
		if ( this.rPolicy ) {
			return this.nextTask();
		}

		$.ajax( {
			url: mw.config.get( 'wgScript' ),
			dataType: 'html',
			data: {
				action: 'render',
				title: RR.rConfig.reasonPage,
				uselang: lang
			},
			cache: true,
			success: function ( result ) {
				RR.rPolicy = $( result ).find( '#onlyinclude' );
				RR.nextTask();
			},
			error: function ( x, status, error ) {
				RR.fail( 'RenameLink: Error getting policy. Server status: ' + x.status + ' - Error: ' + error );
			}
		} );
	},
	rAbort: function ( $el, text ) {
		this.tasks = [];
		setTimeout( function () {
			RR.$txtReason.removeAttr( 'title' );
			RR.$selRationale.removeAttr( 'title' );
			RR.$txtNewName.removeAttr( 'title' );
			RR.$dButtons.button( 'option', 'disabled', false );
			$el.removeClass( 'ui-state-error' );
		}, 4000 );
		this.showProgress();
		$el.attr( 'title', text ).trigger( 'focus' );
		$el.addClass( 'ui-state-error' );
	},
	rCheckInputs: function () {
		this.showProgress( this.rI18n.progress.input );
		this.destination = this.rNewName = this.cleanFileName( this.$txtNewName.val() ).replace( /^File:/, '' );
		if ( !Number( this.$selRationale.val() ) ) {
			return this.rAbort( this.$selRationale, this.rI18n.invalidRationale );
		}

		if ( this.rNewName.length < 5 ) {
			return this.rAbort( this.$txtNewName, this.rI18n.nameToShort );
		}

		if ( this.rNewName.length > 255 ) {
			return this.rAbort( this.$txtNewName, this.rI18n.genericFailure );
		}

		if ( this.rNewName === this.cleanFileName( pn ).replace( /^File:/, '' ) ) {
			return this.rAbort( this.$txtNewName, this.rI18n.newName );
		}

		// Test whether user selected first reason
		if ( Number( this.$selRationale.val() ) === 1 ) {
			var query = {
				prop: 'imageinfo',
				iiprop: 'user',
				iilimit: 100,
				titles: pn.replace( /_/g, ' ' )
			};
			return this.queryAPI( query, 'rCheckInputsCB' );
		}
		this.nextTask();
	},
	rCheckInputsCB: function ( result ) {
		if ( !result || !result.query || !result.query.pages ) {
			return this.nextTask();
		}

		var hasRevisions,
			isByUser;
		$.each( result.query.pages, function ( id, pg ) {
			var info = pg.imageinfo;
			if ( info ) {
				hasRevisions = true;
				for ( var i = 0, ii; i < info.length; i++ ) {
					ii = info[ i ];
					if ( ii.user === user ) {
						isByUser = true;
						break;
					}
				}
			}
		} );
		if ( hasRevisions && !isByUser ) {
			return this.rAbort( this.$selRationale, this.rI18n.notTheUploader );
		} else {
			return this.nextTask();
		}

	},
	rFileExists: function ( /* r*/ ) {
		return this.rAbort( this.$txtNewName, this.rI18n.moveOtherDestination );
	},
	rIsOnBlackList: function () {
		var query = {
			action: 'titleblacklist',
			tbtitle: 'File:' + this.rNewName,
			tbaction: 'create'
		};
		this.showProgress( this.rI18n.progress.blacklisted );
		this.queryAPI( query, 'rIsBlacklistedCB' );
	},
	rIsBlacklistedCB: function ( result ) {
		if ( !result || !result.titleblacklist || !result.titleblacklist.result ) {
			throw new Error( 'RenameLink: result.titleblacklist is undefined.' );
		}

		if ( result.titleblacklist.result === 'blacklisted' ) {
			return this.rAbort( this.$txtNewName, this.rI18n.blacklisted );
		}

		return this.nextTask();
	},
	rSendRequest: function () {
		this.edittoken = this.edittoken || mw.user.tokens.get( 'csrfToken' );
		var doEdit = function ( result ) {
			var editType,
				comRationale = RR.rPolicy.data( 'com-rowmap' )[ Number( RR.$selRationale.val() ) ],
				newText = RR.rConfig.addTmpl
					.replace( '%NEWFILE%', RR.rNewName )
					.replace( '%REASON%', RR.$txtReason.val() )
					.replace( '%NUMBER%', comRationale );

			if ( result ) {
				newText += result.replace( RR.rConfig.regRmvTmpl, '' );
				editType = 'text';
			} else {
				editType = 'prependtext';
			}

			var page = {
				title: pn.replace( /_/g, ' ' ),
				text: newText,
				editType: editType,
				tags: 'RenameLink'
			};
			RR.showProgress( RR.rI18n.progress.edit );
			RR.savePage( page, RR.rConfig.summary
				.replace( '%NEWFILE%', RR.rNewName )
				.replace( '%REASON%', RR.$txtReason.val() )
				.replace( '%NUMBER%', comRationale )
				.replace( ' Reason: ;', '' ), 'nextTask' );
		};
		$.get( mw.config.get( 'wgScript' ), {
			action: 'raw',
			title: pn,
			maxage: 0,
			// smaxage: 0,
			dummy: Math.round( Math.random() * 1073741824 )
		}, doEdit )
			.fail( function ( jqXHR, textStatus/* , errorThrown*/ ) {
				if ( jqXHR.status === 404 ) {
					return doEdit( '' );
				}

				RR.fail( 'Error retrieving wikitext. Server status ' + jqXHR.status + '<br>\nERR: ' + textStatus + '.' );
			} );
		this.showProgress( this.rI18n.progress.load );
	},
	rReady: function ( /* r*/ ) {
		this.showProgress();
		document.location.reload();
	},
	rRun: function () {
		if ( RR.initDone ) {
			return;
		}

		$.extend( true, AjaxQuickDelete, RR );
		RR = AjaxQuickDelete;
		// save some code-lines by assigning similar languages
		// merge languages
		$.extend( true, RR.rI18n, RR.rI18n[ lang ] );
		RR.initDone = true;

		mw.hook( 'aqd.renamerequest.run' ).add( function ( c ) {
			if ( !c ) {
				return;
			}

			if ( c === 'start' ) {
				RR.showProgress( RR.rI18n.progress.policy );
				RR.tasks = [];
				RR.addTask( 'rGetPolicy' );
				RR.addTask( 'rDialog' );
				RR.nextTask();
			} else if ( typeof c === 'object' && 'exec' in c ) {
				RR.showProgress( RR.rI18n.progress.policy );
				RR.$injectNode = $( c.exec );
				RR.tasks.unshift( 'rInjectPolicy' );
				RR.tasks.unshift( 'rGetPolicy' );
				RR.nextTask();
			}
		} );
		mw.hook( 'aqd.renamerequest.ready' ).fire();

	},
	rInit: function ( rI18n ) {
		if ( lang === 'en' || RR.initDone ) {
			return $.Deferred().resolve();
		}

		if ( rI18n && ( rI18n = Object.keys( rI18n ) ).length ) {
			i18n = rI18n;
		}

		// Take fallback language if not exist in i18n list.
		if ( i18n.indexOf( lang ) === -1 ) {
			if (lang == 'zh-hans'||lang == 'zh-cn'||lang == 'zh-my'||lang == 'zh-sg'||lang == 'zh-hant'||lang == 'zh-tw'||lang == 'zh-hk'||lang == 'zh-mo'){
            	// zh-hans zh-cn zh-my zh-sg zh-hant zh-tw zh-hk zh-mo Fallback to zh
            	lang = 'zh'
			} else {
				var i,
					chain = mw.language.getFallbackLanguages();
				for ( i = chain.length - 1; i >= 0; i-- ) {
					if ( i18n.indexOf( chain[ i ] ) !== -1 ) {
						lang = chain[ i ];
					}

				}
            }

		}
		// Import another set of messages, which either
		// does nothing (if the subpage doesn't exist)
		// or re-sets one or more messages into the user language
		return mw.loader.getScript(
			mw.util.wikiScript() + '?title=MediaWiki:RenameRequest.js/' + lang + '.js&action=raw&ctype=text/javascript'
		);
	},
	// Translation
	// This should be changed when gadg ets 2.0 are available
	rI18n: {
		submitButtonLabel: 'Request renaming',
		proceedButtonLabel: 'Proceed',
		cancelButtonLabel: 'Cancel',
		headline: 'Renaming a file',
		lNewName: 'Enter the new name',
		tNewName: 'Enter the desired file name',
		pNewName: 'new name',
		lRationale: 'Rationale according to the policy',
		tRationale: 'Provide a valid reason or select one from the table',
		pRationale: 'Select a reason',
		lReason: 'Additional explanation / reason / justification',
		tReason: 'Optional: Provide details',
		pReason: 'additional reason or justification',
		lAccept: 'I acknowledge that repeated non-justified rename requests will block this feature for me.',
		invalidRationale: 'Select a valid rationale',
		nameToShort: 'Name is too short',
		newName: 'Please specify a *new* name',
		notTheUploader: 'Be honest: You are not the uploader',
		// nameExists: 'There is already a file with the specified file name - Please choose another name',
		blacklisted: 'This name is blacklisted - Please choose another name',
		progress: {
			policy: 'Loading policy',
			input: 'Checking input',
			blacklisted: 'Checking whether the new file name is blacklisted',
			load: 'Loading Wikitext',
			edit: 'Requesting a filemover to move this file'
		}
	},
	// Configuration
	rConfig: {
		reasonPage: 'Template:File renaming reasons/render',
		regRmvTmpl: /\{\{\s*(?:[Rr]ename|[Bb]ad name)[^\{\}]*\}\}(?:\s*)?/,
		addTmpl: '{{Rename|1=%NEWFILE%|2=%NUMBER%|3=%REASON%|user=' + user + '}}\n',
		addReason: '[[COM:FR#FR%NUMBER%|Criterion %NUMBER%]]', // Match Template:Rename/Sub
		reasonSeparator: ' · ', // Match Template:Rename/Sub
		summary: 'Requesting renaming this file to [[File:%NEWFILE%]]; Reason: %REASON%; Criterion %NUMBER%',
		dlg: {
			width: 850,
			height: ( winHeight > 770 ? 'auto' : winHeight )
		},
		helpLink: '<a href="' + mw.util.getUrl( 'Help:RenameLink' ) + '" target="_blank"><img src="//upload.wikimedia.org/wikipedia/commons/4/45/GeoGebra_icon_help.png" alt="?"/></a> '
	}
};

$.when( mw.loader.using( [ 'ext.gadget.AjaxQuickDelete', 'ext.gadget.libJQuery', 'jquery.tipsy', 'mediawiki.language' ] ), $.ready )
	.always( function () {
		mw.hook( 'aqd.renamerequest.i18n' ).add( function ( l ) {
			RR.rInit( l ).always( RR.rRun );
		} );
	} );

}( jQuery, mediaWiki ) );
// </nowiki>