MediaWiki:Gadget-markblocked.js

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
JS-код ниже относится к гаджету: Зачеркнуть ссылки на страницы заблокированных участников (править описание). Его использует около 12 000 учётных записей.

После сохранения или недавних изменений очистите кэш браузера.

$( 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
		var userLinks = '#ca-nstab-user:not(.selected) a, #ca-talk:not(.selected) a';
		var ignoreList = '.mw-changeslist-links a, .mw-changeslist-date, .autocomment a';
		contentLinks = !_wasRunned || !container
			? ( mw.util.$content || $( '.mw-body' ) ).find( 'a' ).add( userLinks ).not( ignoreList )
			: $( container ).find( 'a' ).not( ignoreList );

		// 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 ) {
					$( '<span class="user-blocked-tipbox">#</span>' )
						.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
	var wgAction = mw.config.get( 'wgAction' );
	if ( [ 'view', 'history', 'purge' ].indexOf( wgAction ) > -1 ) {
		if ( wgAction === 'view' && [ 0, 10 ].indexOf( mw.config.get( 'wgNamespaceNumber' ) ) !== -1 ) {
			return;
		}
		
		// 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 );
			}
		} );
	}
} );