MediaWiki:RenameRequest.js
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.
Documentation for this user script can be added at MediaWiki:RenameRequest. |
/**
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>