MediaWiki:Gadget-DiscutiRevisioneBlocco.js
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 sulla 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 = conf.wgRelevantUserName === conf.wgUserName ?
`Utente:${ conf.wgUserName }/Sandbox/Test revisione del blocco` :
'Wikipedia:Richieste di revisione del blocco';
const rfcTemplate = 'Wikipedia:Richieste di revisione del blocco/ModelloDiscussione';
/**
* Cerca le parti commentate e le parti fra i tag nowiki nel wikitesto
* passato in input.
* Restituisce un array di tuple contenenti la posizione di inizio e fine di
* ciascuna parte disabilitata trovata (oppure soltanto la posizione delle
* parti commentate, se così richiesto).
*
* @param {string} wikitext - Il wikitesto in cui effettuare la ricerca
* @param {boolean} commentedPartsOnly - Indica se bisogna restituire solo le
* parti totalmente disabilitate, ovvero quelle commentate con <!-- e -->
* @return {object}
*/
function findDisabledParts( wikitext, commentedPartsOnly ) {
const disabledParts = [];
// regex per trovare sia le parti commentate sia le parti fra i tag nowiki
// (quelle che iniziano con '<!--' possono anche non essere chiuse)
const regex = /(<!--[\s\S]*?(-->|$)|<nowiki(\s*|\s[^>]*)>[\s\S]*?<\/nowiki\s*>)/g;
// ciclo che si ripete per ogni match trovato
while ( true ) {
const match = regex.exec( wikitext );
if ( !match ) break;
// se commentedPartsOnly è true salta le parti fra i tag nowiki,
// comprese le parti fra <!-- e --> che sono annidate al loro interno
if ( commentedPartsOnly && match[ 0 ].startsWith( '<nowiki' ) ) {
continue;
}
// memorizza la posizione di inizio e fine della parte disabilitata
disabledParts.push( [ match.index, regex.lastIndex ] );
}
return disabledParts;
}
/**
* Verifica se la posizione passata in input si trova in una delle parti di
* testo che risultano disabilitate.
* Assume che le parti di testo disabilitate siano state ricavate con la
* funzione findDisabledParts.
*
* @param {int} index - La posizione da verificare
* @param {object} disabledParts - L'array delle tuple contenenti la posizione
* di inizio e fine delle parti di testo disabilitate
* @return {boolean}
*/
function isInDisabledPart( index, disabledParts ) {
return disabledParts.some( ( [ start, end ] ) => index >= start && index < end );
}
/**
* Verifica se è stato compilato solo il parametro 'motivo' del
* template:Revisione blocco passato in input.
*
* @param {string} templateText - Il testo del template
* @return {boolean}
*/
function isAppealNew( templateText ) {
const commentedParts = findDisabledParts( templateText, true );
// rimuove le parti commentate
for ( let i = commentedParts.length - 1; i >= 0; i-- ) {
const [ start, end ] = commentedParts[ i ];
templateText = templateText.substring( 0, start ) + templateText.substring( end );
}
return !/\|\s*(esito|link discussione)\s*=\s*[^\s\|\}]/.test( templateText ) &&
/\|\s*motivo\s*=\s*[^\s\|\}]/.test( templateText );
}
/**
* Cerca i template:Revisione blocco nel wikitesto passato in input.
* Restituisce il testo del primo template che corrisponde a una nuova richiesta.
*
* @param {string} wikitext - Il wikitesto in cui effettuare la ricerca
* @return {string}
*/
function findNewAppeal( wikitext ) {
let newAppeal;
const disabledParts = findDisabledParts( wikitext, false );
const templateNameRegex = isOwnSandbox() ?
/\{\{\s*[Rr]evisione *blocco(\/Sandbox)?\s*\|/g :
/\{\{\s*[Rr]evisione *blocco\s*\|/g;
// ciclo che si ripete per ogni match di inizio template:Revisione blocco
// trovato (es. '{{Revisione blocco' o '{{revisione blocco')
while ( true ) {
const templateNameMatch = templateNameRegex.exec( wikitext );
if ( !templateNameMatch ) break;
// salta i match di inizio template trovati nelle parti di testo disabilitate
if ( isInDisabledPart( templateNameMatch.index, disabledParts ) ) {
continue;
}
// mantiene il conto delle parentesi graffe aperte trovate
let unclosedBracketCount = 2;
const bracketRegex = /(\{\{ *(?=[^ \{])|\}\})/g;
const stringAfterTemplateNameMatch = wikitext.substring(
templateNameRegex.lastIndex
);
// ciclo che si ripete per ogni match di doppie parantesi graffe (sia
// aperte sia chiuse) trovate nel testo che viene dopo le graffe aperte
// matchate nel ciclo esteriore
while ( true ) {
const bracketMatch = bracketRegex.exec( stringAfterTemplateNameMatch );
if ( !bracketMatch ) break;
const bracketMatchStart = templateNameRegex.lastIndex + bracketMatch.index;
const bracketMatchEnd = templateNameRegex.lastIndex + bracketRegex.lastIndex;
// controlla che le doppie parentesi graffe trovate non siano
// disabilitate e aggiorna il conto delle graffe aperte
if ( isInDisabledPart( bracketMatchStart, disabledParts ) ) {
continue;
} else if ( bracketMatch[ 0 ] === '{{' ) {
unclosedBracketCount += 2;
} else if ( bracketMatch[ 0 ] === '}}' ) {
unclosedBracketCount -= 2;
}
// estrae il testo del template:Revisione blocco appena il conto
// delle parentesi graffe aperte si azzera
if ( unclosedBracketCount === 0 ) {
const templateText = wikitext.substring(
templateNameMatch.index,
bracketMatchEnd
);
// verifica che il template estratto sia quello cercato
if ( isAppealNew( templateText ) ) {
newAppeal = templateText;
}
break;
}
}
if ( newAppeal ) 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 il contenuto di una pagina.
*
* @param {object} customParams - I parametri della chiamata che si
* integrano con quelli precompilati (o li sovrascrivono)
* @return {jQuery.Promise}
*/
function getContent( customParams ) {
return new mw.Api( {
parameters: {
action: 'query',
format: 'json',
formatversion: 2,
prop: 'revisions',
rvprop: [ 'ids', 'content', 'timestamp' ],
rvslots: 'main'
}
} ).get( customParams );
}
/**
* Crea la discussione sulla revisione richiesta dall'utente bloccato 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;
// ottiene il wikitesto della talk visualizzata e cerca al suo interno
// il template della richiesta di revisione aperta
progressMsgHandler( mw.msg( 'fetchingTalkPage' ) );
return getContent( { pageids: conf.wgArticleId } ).then( result => {
currentRevision = result.query.pages[ 0 ].revisions[ 0 ];
newAppeal = findNewAppeal( currentRevision.slots.main.content );
if ( !newAppeal ) {
return $.Deferred().reject( 'templatemissing' );
}
// compila il modello predefinito della discussione (il motivo della
// richiesta di revisione è ottenuto tramite subst del t:Revisione blocco)
const text = `{{subst:${ rfcTemplate }` +
'|nome richiedente=' + conf.wgRelevantUserName +
'|motivo richiesta=' + newAppeal.replace( '{{', '{{subst:' ) +
'|oldid motivo=' + currentRevision.revid +
'|data apertura discussione=' + currentDate +
'|primo parere=' + reviewingAdminComment +
'}}';
// crea la pagina dedicata alla discussione sulla revisione del blocco
progressMsgHandler( mw.msg( 'creatingRfC', link( rfcTitle ) ) );
return editContent( {
title: rfcTitle,
text: text,
summary: mw.msg( 'rfcEditSummary' ),
createonly: 1,
watchlist: 'watch'
} );
} ).then( () => {
const archivePage = `${ rfcRootPage }/${ conf.wgRelevantUserName }`;
// linka la pagina di discussione nell'archivio delle discussioni che
// si sono tenute sui blocchi dello stesso utente
progressMsgHandler( mw.msg( 'updatingArchive', link( archivePage ) ) );
return editContent( {
title: archivePage,
appendtext: `\n# [[${ rfcTitle }]]`,
summary: mw.msg( 'archiveEditSummary', currentDate )
} );
} ).then( () => {
// include la pagina di discussione nella pagina di servizio dove sono
// elencate le discussioni sulle revisioni in corso
progressMsgHandler( mw.msg( 'transcludingRfC', link( rfcRootPage ) ) );
return editContent( {
title: rfcRootPage,
appendtext: `\n\n{{${ rfcTitle }}}`,
summary: mw.msg( 'rfcRootPageEditSummary', conf.wgRelevantUserName )
} );
} ).then( () => {
// aggiorna il template:Revisione blocco nella talk dell'utente
// compilando il parametro "link discussione" col nome della pagina
// di discussione appena creata
progressMsgHandler( mw.msg( 'notifyingUser' ) );
const text = currentRevision.slots.main.content.replace(
newAppeal,
newAppeal.replace( /(\|\s*link discussione\s*= ?|(?=\n? *\}\}$))/, match => {
if ( match.includes( 'link discussione' ) ) {
return match + rfcTitle;
} else {
const pre = /\n *\|/.test( newAppeal ) ? '\n' : '';
return `${ pre }|link discussione=${ rfcTitle }`;
}
} )
);
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 ) );