MediaWiki:Gadget-markblocked.js
Возможно, этот код документирован.
$( function () {
var _config = {
mbNoAutoStart: false,
mbTooltip: ';$1 blocked ($2) by $3: $4 ($5 ago)',
mbTempStyle: 'opacity:0.7; text-decoration:line-through;',
mbIndefStyle: 'opacity:0.4; font-style:italic; text-decoration:line-through;',
mbTipBox: null,
mbTipBoxStyle: 'font-size:85%; background:#FFFFF0; border:1px solid #FEA; padding:0 0.3em; color:#AAA;',
mbLoadingOpacity: 0.85
},
_wasRunned = false,
_api,
_userNS = [],
_userTitleRX,
_articleRX,
_scriptRX,
_$portletLink,
_users = {},
_processedLinks = [];
/******* UTIL *******/
//20081226220605 or 2008-01-26T06:34:19Z -> date
function parseTS( ts ) {
var m = ts.replace( /\D/g, '' ).match( /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ );
return new Date ( Date.UTC( m[ 1 ], m[ 2 ] - 1, m[ 3 ], m[ 4 ], m[ 5 ], m[ 6 ] ) );
}
function inHours( ms ) { //milliseconds -> "2:30" or 5,06d or 21d
var mm = Math.floor( ms / 60000 );
if ( !mm ) {
return Math.floor( ms / 1000 ) + 's';
}
var hh = Math.floor( mm / 60 );
mm = mm % 60;
var dd = Math.floor( hh / 24 );
hh = hh % 24;
if ( dd ) {
return dd + ( dd < 10 ? '.' + zz( hh ) : '' ) + 'd';
}
return hh + ':' + zz( mm );
}
function zz( v ) { // 6 -> '06'
if ( v <= 9 ) {
v = '0' + v;
}
return v;
}
/******* PUBLIC *******/
function markBlocked( container ) {
var contentLinks, userLinks, users, user, promises, waitingCSS;
// Find all links in the entire document on first run or in the provided container
contentLinks = !_wasRunned || !container
? ( mw.util.$content || $( '.mw-body' ) ).find( 'a' ).add( '#ca-nstab-user a' )
: $( container ).find( 'a' );
// Find all user links and save them: { 'users': [<link1>, <link2>, ...], 'user2': [<link3>, <link3>, ...], ... }
userLinks = {};
contentLinks.each( function( i, link ) {
if ( _processedLinks.indexOf( link ) !== -1 ) {
return;
}
user = getLinkUser( link );
if ( user ) {
if ( !userLinks[user] ) {
userLinks[user] = [];
}
userLinks[user].push( link );
_processedLinks.push( link );
}
} );
// Filter users whose data need to be retrieved into an array
users = Object.keys( userLinks ).filter( function ( user ) {
return !_users[user];
});
if ( !users || users.length === 0 ) {
markLinks( userLinks );
return;
}
// API requests
waitingCSS = mw.util.addCSS( 'a.userlink {opacity:' + _config.mbLoadingOpacity + '}' );
promises = [];
while ( users.length > 0 ) {
promises.push(
request( users.splice( 0, 50 ) )
);
}
$.when.apply($, promises).always( function () {
markLinks( userLinks );
waitingCSS.disabled = true;
_$portletLink && _$portletLink.remove();
} );
if ( !_wasRunned ) {
_wasRunned = true;
}
}
function getLinkUser( link ) {
var ma, pgTitle, user,
$link = $( link ),
url = $link.attr( 'href' );
if ( !url || url.charAt( 0 ) !== '/' ) {
return;
}
if ( ma = _articleRX.exec( url ) ) {
pgTitle = ma[ 1 ];
} else if ( ma = _scriptRX.exec( url ) ) {
pgTitle = ma[ 1 ];
} else {
return;
}
pgTitle = decodeURIComponent( pgTitle ).replace( /_/g, ' ' );
user = _userTitleRX.exec( pgTitle );
if ( !user ) {
return;
}
user = user[ 2 ];
if ( user === 'К удалению' ) {
return;
}
$link.addClass( 'userlink' );
return user;
}
function request( users ) {
var params = {
action: 'query',
list: 'blocks',
bklimit: 100,
bkusers: users,
bkprop: [ 'user', 'by', 'timestamp', 'expiry', 'reason', 'flags' ],
format: 'json'
};
return _api
.post( params )
.then( response );
}
function response( data, xhr ) {
var list, user,
serverTime = new Date( xhr.getResponseHeader('Date') );
if ( !data || !data.query || !data.query.blocks ) {
return;
}
list = data.query.blocks;
list.forEach( function ( item, i ) {
user = {
name: item.user,
data: item,
partial: ''
};
if ( /^in/.test( user.data.expiry ) ) {
user.class = 'user-blocked-indef';
user.blTime = user.data.expiry;
} else {
user.class = 'user-blocked-temp';
user.blTime = inHours ( parseTS( user.data.expiry ) - parseTS( user.data.timestamp ) );
}
if ( 'partial' in user.data ) {
user.class = 'user-blocked-partial';
user.partial = ' partial';
}
user.message = _config.mbTooltip
.replace( '$1', user.partial )
.replace( '$2', user.blTime )
.replace( '$3', user.data.by )
.replace( '$4', user.data.reason )
.replace( '$5', inHours ( serverTime - parseTS( user.data.timestamp ) ) );
// Export user data
_users[user.name] = user;
} );
}
function markLinks( userLinks ) {
var user, $link;
$.each( userLinks, function ( userName, links ) {
user = _users[userName];
if ( !user ) {
return;
}
links.forEach( function ( link ) {
$link = $( link ).addClass( user.class );
if ( _config.mbTipBox ) {
$( '<img class="user-blocked-tipbox">#</img>' )
.attr( 'title', user.message )
.insertBefore( $link );
} else {
$link.attr( 'title', $link.attr( 'title' ) + user.message );
}
} );
} );
}
function prepare() {
var wgNamespaceIds;
// Merge user config
_config = $.extend( _config, {
mbNoAutoStart: window.mbNoAutoStart,
mbTooltip: window.mbTooltip,
mbTempStyle: window.mbTempStyle,
mbIndefStyle: window.mbIndefStyle,
mbTipBox: window.mbTipBox,
mbTipBoxStyle: window.mbTipBoxStyle,
mbLoadingOpacity: window.mbLoadingOpacity
} );
_api = new mw.Api();
// Get all aliases for user: & user_talk:
wgNamespaceIds = mw.config.get( 'wgNamespaceIds' );
$.each( wgNamespaceIds, function( ns, id ) {
if ( [ 2, 3 ].indexOf( id ) !== -1 ) {
_userNS.push( ns.replace( /_/g, ' ' ) + ':' );
}
} );
// RegExp for all titles that are User: | User_talk: | Special:Contributions/ (localized) | Special:Contributions/ (for userscripts)
_userTitleRX = new RegExp( '^'
+ '(' + _userNS.join( '|' )
+ '|Служебная:Вклад\\/|Special:Contributions\\/'
+ ')'
+ '([^\\/#]+)$', 'i' );
//RegExp for links
_articleRX = new RegExp(
'^(?:' + mw.config.get( 'wgServer' ) + ')?' +
mw.config.get( 'wgArticlePath' ).replace( '$1', '' ) + '([^#]+)'
);
_scriptRX = new RegExp(
'^(?:' + mw.config.get( 'wgServer' ) + ')?' +
mw.config.get( 'wgScript' ) + '\\?title=([^#&]+)'
);
// Add custom css
mw.util.addCSS( '\
.mediawiki .user-blocked-temp {' + _config.mbTempStyle + '}\
.mediawiki .user-blocked-indef {' + _config.mbIndefStyle + '}\
.mediawiki .user-blocked-tipbox {' + _config.mbTipBoxStyle + '}\
' );
}
// Export (some users can use method with custom context)
window.markBlocked = markBlocked;
// Start on some pages
switch ( mw.config.get( 'wgAction' ) ) {
case 'edit':
case 'submit':
case 'delete':
break;
case 'view':
if ( [ 0, 10 ].indexOf( mw.config.get( 'wgNamespaceNumber' ) ) !== -1 ) {
break;
}
// Otherwise continue with default
default: // 'history', 'purge'
// In case if the gadget is loaded directly by URL
mw.loader.using( 'mediawiki.util' ).done( function () {
prepare();
if ( _config.mbNoAutoStart ) {
_$portletLink = $( mw.util.addPortletLink( 'p-cactions', null, 'XX', 'ca-showblocks' ) );
_$portletLink.on( 'click', function( e ) {
e.preventDefault();
markBlocked();
} );
} else {
mw.hook( 'wikipage.content' ).add( markBlocked );
mw.hook( 'global.userlinks' ).add( markBlocked );
}
} );
}
} );