MediaWiki:Gadget-QuickResponses.js: differenze tra le versioni
miglioro gestione messaggi per le protezioni |
m una versione importata |
(Nessuna differenza)
|
Versione attuale delle 22:40, 30 ago 2020
/**
* Al caricamento di determinate pagine di servizio, rielabora le segnalazioni
* con pannelli a tab per agevolare la consultazione di contributi e registri e
* crea un form per permettere risposte rapide con eventuale commento e dati
* generici di protezione o blocco elaborati in automatico.
*
* @author https://it.wikipedia.org/wiki/Utente:Sakretsu
*/
/* global mediaWiki, jQuery, OO */
( function ( mw, $ ) {
'use strict';
// Il nome completo della pagina visitata
var currentPage = mw.config.get( 'wgPageName' );
// mw.Api utilizzata per leggere dati e rispondere
var api = new mw.Api();
// Limite di performance al numero di segnalazioni più recenti da elaborare
var limit = 50;
// Configurazione di variabili per blocchi e protezioni
var conf = {
blocks: {
action: {
pattern: '<a style="color:#444" href="$1">Blocca</a>',
pageName: 'Speciale:Blocca/$1'
},
getLogsInfo: getBlockInfo,
links: {
Discussioni: {
pageName: 'Discussioni utente:$1',
selector: '.mw-parser-output'
},
Contributi: {
pageName: 'Speciale:Contributi/$1',
selector: '.mw-contributions-list, .mw-pager-navigation-bar, .mw-contributions-footer',
params: { limit: 100 },
booklet: true
},
Cancellati: {
pageName: 'Speciale:ContributiCancellati/$1',
selector: '#mw-content-text > ul, .mw-nextlink'
},
Registri: {
pageName: 'Speciale:Registri/$1',
selector: '#mw-log-deleterevision-submit, .mw-nextlink'
},
Blocchi: {
pageName: 'Speciale:Registri',
selector: '#mw-log-deleterevision-submit, .mw-nextlink',
params: { type: 'block', page: 'Utente:$1' }
}
},
negativealert: 'L\'utente non risulta bloccato',
positivealert: 'L\'utente risulta bloccato',
regex: /(\{\{ *[Vv]andalo[ \n]*\|)/,
subj: '.utente-username'
},
protections: {
action: {
pattern: '<a style="color:#444" href="$1">Imposta protezione</a>',
pageName: '$1',
params: { action: 'protect' }
},
getLogsInfo: getProtectionInfo,
edit: {
pattern: '<a style="color:#444" href="$1">Modifica sezione</a>',
},
links: {
Discussioni: {
getPageName: function( text ) {
var mwTitle = new mw.Title.newFromText( text );
if ( mwTitle && !mwTitle.isTalkPage() ) {
return mwTitle.getTalkPage().getPrefixedText();
}
},
selector: '.mw-parser-output'
},
Cronologia: {
pageName: '$1',
selector: '#mw-history-compare, .mw-nextlink',
params: { action: 'history', limit: 100 },
booklet: true
},
Registri: {
pageName: 'Speciale:Registri',
selector: '#mw-log-deleterevision-submit',
params: { page: '$1' }
},
},
missingexpiry: 'Protezione troppo vecchia, dati mancanti',
missingunprot: 'Non risultano protezioni rimosse',
negativealert: 'La pagina non risulta protetta',
positivealert: 'La pagina risulta protetta',
regex: /(\{\{ *[Rr]ichiesta protezione[ \n]*\|)/,
subj: 'b > a'
}
};
// Pagine di servizio su cui caricare lo script con rispettiva configurazione
var noticeboards = {
'Wikipedia:Richieste_di_protezione_pagina': conf.protections,
'Wikipedia:Vandalismi_in_corso': conf.blocks
};
/**
* Ottiene wikitesto e ID della revisione attualmente visualizzata o,
* in alternativa, dell'ultima revisione della pagina di servizio.
*
* @param {string|null} - ID della revisione visualizzata.
* @return {object} - jQuery.Promise.
*/
function parseContent( revid ) {
var params = {
action: 'parse',
prop: 'revid|wikitext',
format: 'json'
};
if ( revid ) {
params.oldid = revid;
} else {
params.page = currentPage;
}
return api.get( params );
}
/**
* Recupera i dati di blocco cui un utente è attualmente sottoposto.
*
* @param {string} user - Il nome dell'utente.
* @param {function} infoHandler - La funzione da richiamare con i risultati.
*/
function getBlockInfo( user, infoHandler ) {
api.get( {
action: 'query',
list: 'blocks|logevents',
bkusers: user,
letype: 'block',
letitle: 'Utente:' + user,
lelimit: 1,
format: 'json'
} ).done( function ( data ) {
// correzione del timestamp che via API:Blocks risale al primo blocco
if ( data.query.logevents[ 0 ] && data.query.logevents[ 0 ].action == 'reblock' ) {
data.query.blocks[ 0 ].timestamp = data.query.logevents[ 0 ].timestamp;
}
infoHandler( data.query.blocks[ 0 ] );
} ).fail( function ( error ) {
var msg = 'nome utente malscritto, es. caratteri invisibili';
OO.ui.alert( 'Errore API: ' + ( error === 'baduser_bkusers' ? msg : error ) );
} );
}
/**
* Recupera i dati dell'ultima protezione impostata su una pagina.
*
* @param {string} page - Il nome della pagina.
* @param {function} infoHandler - La funzione da richiamare con i risultati.
*/
function getProtectionInfo( page, infoHandler ) {
api.get( {
action: 'query',
list: 'logevents',
leprop: 'type|user|timestamp|details',
letitle: page,
letype: 'protect',
lelimit: 1,
format: 'json'
} ).done( function ( data ) {
var protection = data.query.logevents[ 0 ];
if ( protection ) {
protection.by = protection.user;
if ( protection.params.details ) {
var details = protection.params.details[ 0 ];
if ( new Date() > new Date( details.expiry ) ) {
protection = undefined;
} else {
protection.expiry = details.expiry;
protection.level = details.level == 'sysop' ?
'protezione totale' : 'semiprotezione';
}
}
}
infoHandler( protection );
} ).fail( function ( error ) {
OO.ui.alert( 'Errore API: ' + error );
} );
}
/**
* Funzione di utilità per formattare le date.
*
* @param {object} obj - Gli anni/mesi/settimane/giorni in numeri.
* @return {array} ret - I valori con rispettiva unità di misura.
*/
function formatDateValues( obj ) {
var ret = [];
var conv = {
y: [ 'anno', 'anni' ],
m: [ 'mese', 'mesi' ],
w: [ 'settimana', 'settimane' ],
d: [ 'giorno', 'giorni' ]
};
$.each( obj, function( k, v ) {
if ( v !== 0 ) {
var unit = v == 1 ? conv[ k ][ 0 ] : conv[ k ][ 1 ];
ret.push( v + ' ' + unit );
}
} );
return ret;
}
/**
* Funzione per calcolare la durata di un'azione.
* Adattata da https://it.wikipedia.org/wiki/Modulo:Data.
*
* @param {string} start_date - Il timestamp di inizio.
* @param {string} expiration - Il timestamp di scadenza.
* @return {string} - La durata calcolata.
*/
function getDuration( start_date, expiration ) {
if ( [ 'infinity', 'infinite' ].indexOf( expiration ) !== -1 ) {
return 'infinito';
}
var monthdays = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
var d1 = new Date( start_date );
var d2 = new Date( expiration );
var d1t = d1.getTimezoneOffset(), d2t = d2.getTimezoneOffset();
if ( d1t !== d2t ) {
var d2time = d2.getTime(), diff = 60 * 60 * 1000;
d2.setTime( d2t > d1t ? d2time - diff : d2time + diff );
}
var min = ( d2 - d1 ) / 1000 / 60;
var h = Math.floor( min / 60 );
if ( h > 24 && h < 48 ) {
return h + ' ore';
} else if ( h < 24 ) {
if ( min / 60 >= 1 ) {
min -= h * 60;
h += h === 1 ? ' ora' : ' ore';
return h + ( min !== 0 ? ' e ' + Math.round( min ) + ' minuti' : '' );
} else {
return Math.round( min ) + ' minuti';
}
}
var d1y = d1.getYear(), d1m = d1.getMonth(), d1d = d1.getDate();
var d2y = d2.getYear(), d2m = d2.getMonth(), d2d = d2.getDate();
if ( ( d1y % 100 === 0 ) ? ( d1y % 400 === 0 ) : ( d1y % 4 === 0 ) ) {
monthdays[1] += 1;
}
var obj = {
y: d2y - d1y,
m: ( d2m - d1m + 12 ) % 12,
w: 0,
d: d2d >= d1d ? d2d - d1d : monthdays[d1m] - d1d + d2d
};
if ( obj.y > 0 && ( d1m > d2m || ( d1m == d2m && d1d > d2d ) ) ) {
obj.y -= 1;
}
if ( d1d > d2d ) {
obj.m = ( obj.m === 0 && d1y < d2y ) ? 11 : obj.m - 1;
}
if ( Math.floor( obj.d / 7 ) === obj.d / 7 ) {
obj.w = obj.d / 7;
obj.d = 0;
}
var arr = formatDateValues( obj );
if ( arr.length > 1 ) {
var last = arr.pop();
return arr + ' e ' + last;
} else {
return arr.toString();
}
}
/**
* Aggiunge la risposta a una segnalazione nella pagina di servizio.
*
* @param {number} i - Il numero della segnalazione.
* @param {string} text - La risposta da inserire.
* @param {object} currentrev - La revisione visualizzata.
* @param {function} updateHandler - La funzione che aggiorna lo stato dell'operazione.
*/
function editContent( i, msg, currentrev, updateHandler ) {
var wikitext, newText, revId;
parseContent().then( function( result ) {
revId = result.parse.revid;
wikitext = result.parse.wikitext[ '*' ];
return api.get( {
action: 'query',
titles: currentPage,
indexpageids: 1,
prop: 'revisions',
rvprop: 'ids',
format: 'json'
} );
} ).then( function( result ) {
i = i * 2 + 2;
var getSplits = function ( str ) {
return str.split( conf.regex );
};
var sliceSplits = function ( splits, newsplits ) {
return splits.slice( -( 2 * limit + 1 + ( newsplits || 0 ) ) );
};
var splits = getSplits( wikitext );
var unchanged = true;
var data = result.query.pages[ result.query.pageids[ 0 ] ].revisions[ 0 ];
if ( data.revid !== currentrev.revid ) {
var origsplits = getSplits( currentrev.wikitext[ '*' ] );
var newsplits = Math.max( 0, splits.length - origsplits.length );
origsplits = sliceSplits( origsplits );
splits = sliceSplits( splits, newsplits );
unchanged = origsplits[ i ].indexOf( splits[ i ].trim() ) !== -1;
} else {
splits = sliceSplits( splits );
}
if ( data.revid !== revId || !unchanged ) {
return $.Deferred().reject( 'editconflict' );
}
wikitext = wikitext.substring( 0, wikitext.lastIndexOf( splits.join( '' ) ) );
var match = splits[i].match( /(^[\s\S]+?)(?:(\n:+)(.+))?(\n*$|\n+==[\s\S]+$)/ );
msg = match[ 2 ] ? match[ 2 ] + ':' + msg : '\n:' + msg;
splits[i] = match[ 1 ] + ( match[ 2 ] ? match[ 2 ] + match[ 3 ] : '' ) + msg + match[ 4 ];
newText = wikitext + splits.join( '' );
return api.postWithToken( 'csrf', {
action: 'edit',
title: currentPage,
text: newText,
summary: 'segnalazione evasa',
tags: 'quick-responses',
watchlist: 'nochange'
} );
} ).done( function () {
updateHandler( true );
} ).fail( function ( code, data ) {
updateHandler( false );
console.log( 'Errore API', code, data );
OO.ui.alert( code === 'editconflict' ?
'Errore: conflitto di modifiche' :
'Errore alla modifica della pagina'
);
} );
}
/**
* Ottiene il confronto tra la revisione indicata e quella precedente.
*
* @param {object} url - L'url della revisione.
* @param {function} diffHandler - La funzione da richiamare col risultato.
*/
function getDiff( url, diffHandler ) {
var oldid = mw.util.getParamValue(
'oldid',
url.href
);
return api.get( {
action: 'compare',
prop: 'diff',
fromrev: oldid,
torelative: 'prev'
} ).then( function( data ) {
diffHandler( data.compare[ '*' ] );
} );
}
/**
* Crea un booklet per sfogliare rapidamente i diff più recenti di
* una cronologia o un elenco di contributi.
*
* @param {object} urls - Gli url da cui estrarre i diff.
* @return {object} - Il booklet.
*/
function buildBooklet( urls ) {
var pageIndex = 0;
var $span = $( '<span></span>' );
var targetUrl = function( current ) {
var currentDiff = urls.parents( 'li:eq(' + pageIndex + ')' );
currentDiff.css( 'background-color', current ? '#f8f2ce' : '' );
if ( current ) {
var date = currentDiff.find( 'a.mw-changeslist-date' ).text();
var user = currentDiff.find( 'a.mw-userlink' ).text();
var label = 'Cambia diff';
if ( date ) {
label += ' <small>(attuale ';
label += user ? 'di ' + user + ' ' : '';
label += 'in data ' + date + ')</small>';
}
$span.html( label );
}
};
var changePage = function ( direction ) {
targetUrl( false );
pageIndex = ( urls.length + pageIndex + direction ) % urls.length;
bookletLayout.setPage( 'page-' + pageIndex );
targetUrl( true );
getDiff( urls[ pageIndex ], function( diff ) {
$table.html( colgroup + diff );
} );
};
var navigationField = new OO.ui.FieldLayout(
new OO.ui.ButtonGroupWidget( {
items: [
new OO.ui.ButtonWidget( {
data: 'previous',
icon: 'previous'
} ).on( 'click', function () {
changePage( -1 );
} ),
new OO.ui.ButtonWidget( {
data: 'next',
icon: 'next'
} ).on( 'click', function () {
changePage( 1 );
} )
]
} ),
{
label: $span,
align: 'top'
}
);
var bookletLayout = new OO.ui.BookletLayout( {
expanded: false,
menuPosition: 'top'
} );
var $table = $( '<table></table>' )
.addClass('diff diff-contentalign-left');
var colgroup = '<colgroup><col class="diff-marker">\
<col class="diff-content"><col class="diff-marker">\
<col class="diff-content"></colgroup>';
bookletLayout.addPages( [
new OO.ui.PageLayout( 'page-1', {
expanded: false,
$content: $table
} )
] );
getDiff( urls[ 0 ], function( diff ) {
$table.html( colgroup + diff );
} );
targetUrl( true );
bookletLayout.$element.prepend( navigationField.$element );
return bookletLayout.$element;
}
/**
* Crea tab per consultare agevolmente contributi e registri.
*
* @param {string} subj - Il nome della pagina o dell'utente da esaminare.
* @param {object} subjLinks - I link di utilità del template di segnalazione.
* @param {object} $h2 - L'eventuale intestazione della segnalazione.
* @return {object} - Il pannello a tab.
*/
function buildTabs( subj, subjLinks, $h2 ) {
var indexLayout = new OO.ui.IndexLayout( {
expanded: false
} );
var panelLayout = new OO.ui.PanelLayout( {
expanded: false,
framed: true,
content: [ indexLayout ]
} );
var tabs = [
new OO.ui.TabPanelLayout( 'default', {
expanded: false,
label: subj,
content: [ subjLinks ]
} )
];
$.each( conf.links, function( k, v ) {
var tab = new OO.ui.TabPanelLayout( k, {
expanded: false,
label: k
} ).on( 'active', function () {
var $el = tab.$element;
if ( tab.isActive() && !$el.text() ) {
$el.append( '<p><i>Caricamento...</i></p>' );
var pageName = v.getPageName ? v.getPageName( subj ) :
v.pageName.replace( /\$1/, subj );
var params = Object.assign( {}, v.params );
if ( params && params.page ) {
params.page = params.page.replace( /\$1/, subj );
}
var url = mw.util.getUrl( pageName, params );
var selector = ' ' + v.selector;
$( $el ).load( url + selector, function( response, status, xhr ) {
var notFound = xhr.status === 404 || xhr.status === 400;
if ( status === 'error' && !notFound ) {
var error = xhr.status + " " + xhr.statusText;
$el.html( '<p><i>Errore: ' + error + '</i></p>' );
} else if ( notFound || !$el.text() ||
$el.find( '.mw-contributions-footer' ).length &&
!$el.find( '.mw-contributions-list' ).length ) {
$el.html( '<p><i>Nessun risultato</i></p>' );
} else if ( v.booklet ) {
var urls = $el.find( 'a.mw-changeslist-date' );
if ( urls.length ) {
$el.prepend( buildBooklet( urls ) );
}
}
$( '.mw-checkbox-toggle-controls' ).remove();
} );
}
} );
tabs.push( tab );
} );
indexLayout.addTabPanels( tabs );
if ( conf.edit ) {
var href = $h2.find( '.mw-editsection a' ).last().attr( 'href' );
var editOption = new OO.ui.TabOptionWidget( {
disabled: true,
label: new OO.ui.HtmlSnippet(
conf.edit.pattern.replace( /\$1/, href )
)
} );
indexLayout.getTabs().addItems( editOption );
}
var actionOption = new OO.ui.TabOptionWidget( {
disabled: true,
label: new OO.ui.HtmlSnippet(
conf.action.pattern.replace( /\$1/,
mw.util.getUrl(
conf.action.pageName.replace( /\$1/, subj ),
conf.action.params
) )
)
} );
indexLayout.getTabs().addItems( actionOption );
return panelLayout.$element;
}
/**
* Crea un pulsante di commutazione per accedere al form di risposta.
*
* @param {boolean} closedReport - True se la richiesta risulta evasa.
* @param {function} toggleHandler - La funzione che anima il form.
* @return {object} Il pulsante di commutazione.
*/
function buildToggleButton( closedReport, toggleHandler ) {
var toggleButton = new OO.ui.ToggleButtonWidget( {
classes: [ 'toggle' ],
icon: 'previous',
framed: false,
label: closedReport ? '' : 'Gestisci segnalazione'
} );
toggleButton.on( 'click', function () {
if ( toggleButton.getValue() === true ) {
toggleButton.setIcon( 'next' );
} else {
toggleButton.setIcon( 'previous' );
}
toggleHandler();
} );
return toggleButton.$element;
}
/**
* Crea il form per rispondere rapidamente a una segnalazione.
*
* @param {boolean} reversedResult - True se è una richiesta di sprotezione.
* @param {function} clickHandler - La funzione che implementa i pulsanti.
* @return {object} - Il form.
*/
function buildForm( reversedResult, clickHandler ) {
var $wrapper = $( '<div></div>' );
var fieldset = new OO.ui.FieldsetLayout( {
classes: [ 'form' ]
} );
var input = new OO.ui.MultilineTextInputWidget( {
autosize: true,
placeholder: 'Commento facoltativo',
label: '{{}}'
} );
var field = new OO.ui.FieldLayout( input, {
align: 'top'
} );
var button1 = new OO.ui.ButtonWidget( {
icon: 'check',
label: 'Fatto'
} ).on( 'click', function () {
clickHandler( function ( logsInfo ) {
if ( !reversedResult ) {
if ( !logsInfo || logsInfo.action == 'unprotect' ) {
OO.ui.alert( conf.negativealert );
return;
} else if ( !logsInfo.expiry ) {
OO.ui.alert( conf.missingexpiry );
return;
}
} else if ( logsInfo && logsInfo.action != 'unprotect' ) {
OO.ui.alert( conf.positivealert );
return;
} else if ( !logsInfo ) {
OO.ui.alert( conf.missingunprot );
return;
}
var comment = input.getValue();
var currentuser = mw.config.get( 'wgUserName' );
var msg = '{{fatto}}';
if ( !reversedResult ) {
var duration = getDuration( logsInfo.timestamp, logsInfo.expiry );
msg += ' ', msg += duration == 'infinito' && logsInfo.level ?
logsInfo.level + ' infinita' :
duration + ( logsInfo.level ? ' di ' + logsInfo.level : '' );
}
msg += currentuser !== logsInfo.by ? ' da ' + logsInfo.by : '';
msg += comment ? ' - ' + comment : '';
msg += '--~~' + '~~';
return { msg: msg, button: button1 };
} );
} );
var button2 = new OO.ui.ButtonWidget( {
icon: 'close',
label: 'Non fatto'
} ).on( 'click', function () {
clickHandler( function ( logsInfo ) {
if ( logsInfo && logsInfo.action == 'protect' && !logsInfo.expiry ) {
OO.ui.alert( conf.missingexpiry );
return;
} else if ( !reversedResult && logsInfo && logsInfo.action != 'unprotect' ) {
OO.ui.alert( conf.positivealert );
return;
} else if ( reversedResult && ( !logsInfo || logsInfo.action == 'unprotect' ) ) {
OO.ui.alert( conf.negativealert );
return;
}
var msg = '{{non fatto}} ' + input.getValue() + '--~~' + '~~';
return { msg: msg, button: button2 };
} );
} );
var actionfield = new OO.ui.ActionFieldLayout( button1, button2, {
align: 'inline',
help: 'Cliccando un pulsante risponderai subito alla segnalazione.\
Firma ed eventuali dati del provvedimento (autore, durata) saranno inseriti in automatico.\
Di seguito ti sarà mostrato lo stato dell\'operazione.',
$overlay: $wrapper
} );
fieldset.addItems( [ field, actionfield ] );
return $wrapper.append( fieldset.$element );
}
/**
* Avvia il caricamento di form e tab per ogni segnalazione prevista.
*/
function loadQuickResponses() {
var style = '.form-div { display: table; float: right; overflow: hidden }' +
'.form { padding: 1em } .toggle { display: table-cell } h3 { clear: right }' +
'.oo-ui-indexLayout-stackLayout > .oo-ui-panelLayout { max-height: 80vh; padding: .5em }' +
'.oo-ui-panelLayout-framed { clear: right; margin: 1.5em 0 1em }' +
'.h2-inv { position: absolute; visibility: hidden }';
mw.util.addCSS( style );
var currentrev;
parseContent( mw.config.get( 'wgRevisionId' ) ).done( function ( result ) {
currentrev = result.parse;
} );
mw.hook( 'wikipage.content' ).add( function ( $content ) {
var outerWidth, width;
var reports = $content.find( '.report' ).slice( -limit );
reports.each( function ( i, el ) {
var subj = $( this ).find( conf.subj ).text().trim();
var closedReport = $( this ).nextUntil( '.report' ).find( '.image' ).length > 0;
var reversedResult = $( this ).hasClass( 'unprotect' );
var $form;
var $toggleButton = buildToggleButton( closedReport, function () {
if ( !$form ) {
$form = buildForm( reversedResult, function ( responseHandler ) {
conf.getLogsInfo( subj, function ( logsInfo ) {
var obj = responseHandler( logsInfo );
if ( obj ) {
obj.button.setDisabled( true );
status.html( '<i>Sto effettuando la modifica...</i>' );
editContent( i, obj.msg, currentrev, function ( success ) {
if ( success ) {
status.html( '<i>Modifica effettuata:</i><br />' + obj.msg );
} else {
obj.button.setDisabled( false );
status.html( '<i>Modifica non riuscita.</i>' );
}
} );
}
} );
} );
$form.appendTo( $div );
if ( !outerWidth ) {
outerWidth = $form.outerWidth();
width = $form.width();
}
$form.css( { 'margin-right': -outerWidth, 'width': width } );
var status = $( '<p>' ).appendTo( $form );
}
var mr = $form.css( 'marginRight' );
$form.animate( { marginRight: mr === '0px' ? -outerWidth : 0 } );
} );
var $div = $( '<div class="form-div"></div>' ).append( $toggleButton );
$div.insertAfter( this );
var $h2 = conf.edit && $( this ).prevAll( 'h2' ).first().addClass( 'h2-inv' );
var tabs = buildTabs( subj, $( this ).contents(), $h2 );
$( this ).after( tabs ).hide();
} );
} );
}
$( function () {
conf = noticeboards[ currentPage ];
if ( mw.config.get( 'wgAction' ) === 'view' && conf ) {
loadQuickResponses();
}
} );
}( mediaWiki, jQuery ) );