MediaWiki:Gadget-DiscutiRevisioneBlocco.js

Da Wikipedia, l'enciclopedia libera.
Vai alla navigazione Vai alla ricerca

Questa pagina definisce alcuni parametri di aspetto e comportamento generale di tutte le pagine. Per personalizzarli vedi Aiuto:Stile utente.


Nota: dopo aver salvato è necessario pulire la cache del proprio browser per vedere i cambiamenti (per le pagine globali è comunque necessario attendere qualche minuto). Per Mozilla / Firefox / Safari: fare clic su Ricarica tenendo premuto il tasto delle maiuscole, oppure premere Ctrl-F5 o Ctrl-R (Command-R su Mac); per Chrome: premere Ctrl-Shift-R (Command-Shift-R su un Mac); per Konqueror: premere il pulsante Ricarica o il tasto F5; per Opera può essere necessario svuotare completamente la cache dal menù Strumenti → Preferenze; per Internet Explorer: mantenere premuto il tasto Ctrl mentre si preme il pulsante Aggiorna o premere Ctrl-F5.

/**
 * Questo accessorio automatizza tutte le operazioni necessarie per avviare la
 * discussione della richiesta di revisione di un blocco.
 * Quando l'utente clicca il pulsante "Crea la pagina di discussione" del
 * template:Revisione blocco, visualizza una richiesta di conferma e poi
 * riceve riscontro delle operazioni in corso in una finestra di dialogo.
 *
 * @author https://it.wikipedia.org/wiki/Utente:Sakretsu
 */
/* global mediaWiki, jQuery, OO */

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

	const conf = mw.config.get( [
		'wgArticleId',
		'wgCurRevisionId',
		'wgNamespaceNumber',
		'wgRelevantUserName',
		'wgRevisionId',
		'wgUserGroups',
		'wgUserName'
	] );
	const dependencies = [
		'ext.gadget.CommentWidget',
		'mediawiki.api',
		'mediawiki.util',
		'oojs-ui-core',
		'oojs-ui-widgets',
		'oojs-ui-windows'
	];
	const monthNames = [
		'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno',
		'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'
	];
	const rfcRootPage = isOwnSandbox() ?
		`Utente:${ conf.wgUserName }/Sandbox/Test revisione del blocco` :
		'Wikipedia:Richieste di revisione del blocco';
	const rfcTemplate = 'Wikipedia:Richieste di revisione del blocco/ModelloDiscussione';
	const appealTemplate = isOwnSandbox() ? 'Revisione blocco/Sandbox' : 'Revisione blocco';

	/**
	 * Verifica se è stato compilato solo il parametro "motivo" del
	 * template:Revisione blocco passato in input.
	 * 
	 * @param {object} [appeal] Il template:Revisione blocco da esaminare
	 * @return {boolean}
	 */
	function isAppealNew( appeal ) {
		const result = appeal.args.esito,
			discussionLink = appeal.args[ 'link discussione' ],
			reason = appeal.args.motivo;

		return ( !result || !result.value.trim() ) &&
			( !discussionLink || !discussionLink.value.trim() ) &&
			( reason && reason.value.trim() );
	}

	/**
	 * Controlla i template:Revisione blocco passati in input.
	 * Restituisce il primo template che corrisponde a una nuova richiesta.
	 * 
	 * @param {object} [appeals] Array di template:Revisione blocco
	 * @return {string}
	 */
	function findNewAppeal( appeals ) {
		let newAppeal;

		for ( const appeal of appeals ) {
			if ( isAppealNew( appeal ) ) {
				newAppeal = appeal;
				break;
			}
		}

		return newAppeal;
	}

	/**
	 * Restituisce una data in formato d mmmm yyyy.
	 * 
	 * @param {object} [date] L'oggetto che rappresenta la data
	 * @return {string}
	 */
	function formatDate( date ) {
		return date
			.toLocaleDateString( 'it-IT', { timeZone: 'Europe/Berlin' } )
			.replace(
				/\/\d\d?\//,
				match => ` ${ monthNames[ match.replaceAll( '/', '' ) - 1 ] } `
			);
	}

	/**
	 * Restituisce il link a una pagina wiki.
	 * 
	 * @param {string} [title] Il titolo della pagina da linkare
	 * @return {string}
	 */
	function link( title ) {
		return `<a href=${ mw.util.getUrl( title ) }>${ title }</a>`;
	}

	/**
	 * Effettua una chiamata API per chiedere la modifica di una pagina.
	 * 
	 * @param {object} [customParams] I parametri della chiamata che si
	 *  integrano con quelli precompilati (o li sovrascrivono)
	 * @return {jQuery.Promise}
	 */
	function editContent( customParams ) {
		return new mw.Api( {
				parameters: {
					action: 'edit',
					format: 'json',
					watchlist: 'nochange'
				}
			} ).postWithToken( 'csrf', customParams );
	}

	/**
	 * Effettua una chiamata API per ottenere l'ultima versione di una pagina,
	 * compreso un array di tutti i template:Revisione blocco in essa contenuti.
	 * 
	 * @param {number} [pageid] L'ID della pagina
	 * @return {jQuery.Promise}
	 */
	function getCurrentRevision( pageid ) {
		return $.ajax( {
		  url: '//itwikiapi.toolforge.org/v1/revisions',
		  data: {
		    pageid: pageid,
		    rvprop: 'ids|content|timestamp',
		    templatenames: appealTemplate,
		  }
		} );
	}

	/**
	 * Inserisce il link alla discussione della revisione del blocco nel parametro
	 * "link discussione" del template:Revisione blocco che corrisponde alla
	 * richiesta di revisione aperta.
	 * Restituisce il wikitesto modificato.
	 * 
	 * @param {string} [link] Il link alla discussione
	 * @param {object} [appeal] Il template della richiesta aperta
	 * @param {string} [wikitext] Il wikitesto da modificare
	 * @return {string}
	 */
	function insertDiscussionLink( link, appeal, wikitext ) {
		let name, value, startIndex, endIndex;

		if ( appeal.args[ 'link discussione' ] ) {
			( { name, value } = appeal.args[ 'link discussione' ] );
			value = value.replace( /(\n?$)/, link + '$1' );
			[ startIndex, endIndex ] = appeal.args[ 'link discussione' ].span;
		} else {
			// aggiunge il parametro mantenendo la stessa spaziatura di "motivo"
			( { name, value } = appeal.args.motivo );
			name = name.replace( 'motivo', 'link discussione' );
			value = value.replace( value.trim(), link );
			startIndex = endIndex = appeal.span[ 1 ] - 2;
		}

		return wikitext.substring( 0, startIndex )
			+ `|${ name }=${ value }`
			+ wikitext.substring( endIndex );
	}

	/**
	 * Crea la discussione della richiesta di revisione del blocco e la linka o
	 * include in tutte le pagine correlate.
	 * 
	 * @param {string} [reviewingAdminComment] Il parere dell'admin che crea la discussione
	 * @param {function} [progressMsgHandler] La funzione per mostrare progressi
	 * @param {function} [errorMsgHandler] La funzione per mostrare errori
	 * @param {function} [successMsgHandler] La funzione per mostrare successo
	 * @return {jQuery.Promise}
	 */
	function createRequestForComment(
		reviewingAdminComment, progressMsgHandler, errorMsgHandler, successMsgHandler
	) {
		const currentDate = formatDate( new Date() );
		const rfcTitle = `${ rfcRootPage }/${ conf.wgRelevantUserName }/${ currentDate }`;
		let currentRevision, newAppeal;

		progressMsgHandler( mw.msg( 'fetchingTalkPage' ) );

		// ottiene i dati dell'ultima versione della talk visualizzata
		return getCurrentRevision( conf.wgArticleId ).then( result => {
			if ( !result.query.pages[ 0 ].missing ) {
				currentRevision = result.query.pages[ 0 ].revisions[ 0 ];
				newAppeal = findNewAppeal( currentRevision.templates );
			}

			if ( !newAppeal ) {
				return $.Deferred().reject( 'templatemissing' );
			}

			// compila il modello predefinito delle discussioni delle richieste
			const text = '{{subst:' + rfcTemplate +
				'|nome richiedente=' + conf.wgRelevantUserName +
				'|motivo richiesta=' + newAppeal.args.motivo.value.trim() +
				'|oldid motivo=' + currentRevision.revid +
				'|data apertura discussione=' + currentDate +
				'|primo parere=' + reviewingAdminComment +
				'}}';

			progressMsgHandler( mw.msg( 'creatingRfC', link( rfcTitle ) ) );

			// crea la pagina di discussione della richiesta di revisione del blocco
			return editContent( {
				title: rfcTitle,
				text: text,
				summary: mw.msg( 'rfcEditSummary' ),
				createonly: 1,
				watchlist: 'watch'
			} );
		} ).then( () => {
			const archivePage = `${ rfcRootPage }/${ conf.wgRelevantUserName }`;

			progressMsgHandler( mw.msg( 'updatingArchive', link( archivePage ) ) );

			// linka la pagina di discussione nell'archivio delle discussioni che
			// si sono tenute sui blocchi dello stesso utente
			return editContent( {
				title: archivePage,
				appendtext: `\n# [[${ rfcTitle }]]`,
				summary: mw.msg( 'archiveEditSummary', currentDate )
			} );
		} ).then( () => {
			progressMsgHandler( mw.msg( 'transcludingRfC', link( rfcRootPage ) ) );

			// include la pagina di discussione nella pagina di servizio dove sono
			// elencate le discussioni in corso delle richieste di revisione
			return editContent( {
				title: rfcRootPage,
				appendtext: `\n\n{{${ rfcTitle }}}`,
				summary: mw.msg( 'rfcRootPageEditSummary', conf.wgRelevantUserName )
			} );
		} ).then( () => {
			progressMsgHandler( mw.msg( 'notifyingUser' ) );

			// aggiorna il template:Revisione blocco nella talk dell'utente
			// compilando il parametro "link discussione" col nome della pagina
			// di discussione appena creata
			const text = insertDiscussionLink(
				rfcTitle,
				newAppeal,
				currentRevision.slots.main.content
			);

			// pubblica la modifica
			return editContent( {
				pageid: conf.wgArticleId,
				text: text,
				summary: mw.msg( 'talkPageEditSummary', rfcTitle ),
				starttimestamp: currentRevision.timestamp,
				baserevid: currentRevision.revid
			} );
		} ).done( () => {
			successMsgHandler( mw.msg( 'success' ) );
		} ).fail( code => {
			let errorText = mw.msg( 'errorOccurred' ) + ' ';

			switch( code ) {
				case 'articleexists':
				case 'templatemissing':
					errorText += mw.msg( code );
					break;
				default:
					errorText += mw.msg( 'unknownError', code );
			}

			errorMsgHandler( errorText );
		} );
	}

	/**
	 * Crea una finestra di dialogo che mostra gli aggiornamenti sulle
	 * operazioni in corso.
	 * 
	 * @return {object} - L'oggetto che rappresenta la finestra
	 */
	function createProgressDialog() {
		function ProgressDialog() {
			ProgressDialog.super.call( this,  { size: 'large' } );
		}

		OO.inheritClass( ProgressDialog, OO.ui.Dialog );

		ProgressDialog.static.name = 'progressDialog';
		ProgressDialog.static.title = mw.msg( 'dialogTitle' );

		ProgressDialog.prototype.initialize = function () {
			ProgressDialog.super.prototype.initialize.call( this );

			this.content = new OO.ui.PanelLayout( {
				padded: true,
				expanded: false
			} );
			this.message = new OO.ui.LabelWidget();
			this.closeButton = new OO.ui.ButtonWidget( {
				label: mw.msg( 'closeButtonLabel' )
			} );

			this.content.$element.css( {
				'min-height': '240px'
			} );
			this.title.$element.css( {
				'display': 'block',
				'font-size': '1.5em',
				'padding-bottom': '.25em',
				'text-align': 'center'
			} );
			this.message.$element.css( {
				'display': 'block',
				'font-size': '1.1em'
			} );
			this.closeButton.toggle( false );

			this.content.$element.append(
				this.title.$element,
				this.message.$element,
				this.closeButton.$element
			);
			this.$body.append( this.content.$element );

			this.closeButton.connect( this, { click: 'close' } );
		};

		ProgressDialog.prototype.appendMsg = function ( text, styles ) {
			this.message.$element.append( $( '<p>' ).css( styles ).html( text ) );
			this.updateSize();
		};

		ProgressDialog.prototype.appendProgressMsg = function ( text ) {
			this.appendMsg( `\u2022 ${ text }`, { 'color': '#202122' } );
		};

		ProgressDialog.prototype.appendErrorMsg = function ( text ) {
			this.appendMsg( text, {
				'color': 'red',
				'padding': '.15em 0'
			} );
		};

		ProgressDialog.prototype.appendSuccessMsg = function ( text ) {
			this.appendMsg( text, {
				'font-size': 'large',
				'padding-top': '.25em',
				'text-align': 'center'
			} );
		};

		ProgressDialog.prototype.showCloseButton = function () {
			this.closeButton.toggle( true );
			this.updateSize();
		};

		return new ProgressDialog();
	}

	/**
	 * Verifica se l'utente che sta usando l'accessorio è un amministratore.
	 * 
	 * @return {boolean}
	 */
	function isUserAdmin() {
		return conf.wgUserGroups.includes( 'sysop' );
	}

	/**
	 * Verifica se la pagina visualizzata è una sandbox dell'utente che sta
	 * usando l'accessorio.
	 * 
	 * @return {boolean}
	 */
	function isOwnSandbox() {
		return conf.wgNamespaceNumber === 2 &&
			conf.wgRelevantUserName === conf.wgUserName;
	}

	/**
	 * Verifica se la pagina visualizzata rientra fra quelle dove è previsto che
	 * sia eseguito l'accessorio.
	 * 
	 * @return {boolean}
	 */
	function isPageValid() {
		// consente di testare l'accessorio nelle proprie sandbox
		if ( isOwnSandbox() ) {
			return true;
		}

		return conf.wgNamespaceNumber === 3 &&
			conf.wgRelevantUserName !== null &&
			conf.wgArticleId !== 0 &&
			conf.wgCurRevisionId === conf.wgRevisionId;
	}

	$( () => {
		const newRfCButton = $( '.pulsante-discuti-revisione-blocco' );

		// termina l'esecuzione se riscontra anomalie
		if ( !isPageValid() || !isUserAdmin() || !newRfCButton.length ) return;

		// aspetta il corretto caricamento delle dipendenze prima di procedere
		mw.loader.using( dependencies ).done( () => {
			let windowManager, commentWidget;

			// carica i messaggi di sistema dell'accessorio
			mw.messages.set( require( './DiscutiRevisioneBlocco-Messages.json' ) );

			// modifica il comportamento del pulsante "Crea la pagina di discussione"
			newRfCButton.on( 'click', () => {
				event.preventDefault();

				// avvisa l'utente che non può esserci più di una richiesta aperta
				if ( newRfCButton.length > 1 ) {
					OO.ui.alert( mw.msg( 'toomanyappeals' ), {
						title: mw.msg( 'errorOccurred' )
					} );
					return;
				}

				if ( !commentWidget ) {
					const CommentWidget = require( 'ext.gadget.CommentWidget' );

					// crea un'area di testo dove l'utente può inserire il suo
					// parere sulla revisione del blocco e confermare
					commentWidget = new CommentWidget( {
						commentIndent: '*',
						placeholder: mw.msg( 'commentBodyPlaceholder' ),
						storageId: conf.wgArticleId
					} ).on( 'detach', () => {
						newRfCButton.find( 'input' ).addClass( 'mw-ui-progressive' );
					} ).on( 'confirm', data => {
						if ( !windowManager ) {
							commentWidget.setReadOnly( true );
							commentWidget.confirmButton
								.on( 'click', () => commentWidget.emit( 'confirm' ) )
								.disconnect( commentWidget, { click: 'onConfirmClick' } );

							// crea la finestra che mostra le operazioni in corso
							const progressDialog = createProgressDialog();
							windowManager = new OO.ui.WindowManager();

							$( 'body' ).append( windowManager.$element );
							windowManager.addWindows( [ progressDialog ] );

							// esegue le operazioni tenendo aggiornata la finestra
							createRequestForComment(
								data.value,
								text => progressDialog.appendProgressMsg( text ),
								text => progressDialog.appendErrorMsg( text ),
								text => progressDialog.appendSuccessMsg( text )
							).done(
								() => commentWidget.clearStorage()
							).always(
								() => progressDialog.showCloseButton()
							);
						}

						windowManager.openWindow( 'progressDialog' );
					} );
					commentWidget.disconnect( commentWidget, { confirm: 'teardown' } );
					commentWidget.cancelButton
						.connect( commentWidget, { click: 'detach' } )
						.disconnect( commentWidget, { click: 'tryTeardown' } );
				}

				if ( !commentWidget.isElementAttached() ) {
					newRfCButton.find( 'input' ).removeClass( 'mw-ui-progressive' );
					newRfCButton.after( commentWidget.$element );
				}

				commentWidget.focus().scrollElementIntoView();
			} );
		} );
	} );
}( mediaWiki, jQuery ) );