MediaWiki:Gadget-markblocked.js

From Meta, a Wikimedia project coordination wiki

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
$( function () {
	 
    i18n = {
      
      'en': ';$1 blocked ($2) by $3: Reason: $4',
      'de': '$1; blockiert von $3 mit einer Ablaufzeit von $2; Grund: $4;',
      'bar': '$1; is vo $3 fia "$2" gspiirt worn; Grund d fia is: $4;',
      'nl': '$1; geblokkeerd door $3 met een duur van $2; Reden: $4;',
      'uk': '$1; заблоковано $3 тривалістю $2; Причина: $4;',
      'cs': '$1; blokováno $3 s dobou platnosti $2; důvod: $4;',
      'pt': '$1; bloqueado por $3 com um prazo de validade de $2; razão: $4;',
      'sv': ';$1 blockerad ($2) av $3: anledning: $4',
      'es': ';$1 bloqueado ($2) por $3: razón: $4',
      'vi': ';$1 bị cấm ($2) bởi $3: Lý do: $4',
    };
 if (!window.mbInfinity) {
    i18n = {
      'en': 'infinity',
      'de': 'unbeschränkt',
      'bar': 'ohne Grenz',
      'es': 'para siempre',
      'cs': 'do odvolání',
      'pt': 'infinito',
      'vi': 'vô hạn',
      'zh': '无限期',
      'zh-hans': '无限期',
      'zh-hant': '無限期'
    };
    mbInfinity = i18n[mw.config.get('wgUserLanguage')] || i18n.en;
  }

	var _config = {
			mbTooltip: i18n[mw.config.get('wgUserLanguage')] || i18n.en,
			mbIndefStyle: 'opacity:0.4; font-style: italic; text-decoration: line-through darkred 3px;',
			mbTempStyle: 'opacity:0.7; font-variant-caps: titling-caps; text-decoration: line-through darkorange 2.1px;',
			mbipTempStyle:'opacity:0.7; text-decoration: line-through red 2.1px;',
			mbTipBox: null,
			mbTipBoxStyle: 'font-size:85%; background:#FFFFF0; border:1px solid #FEA; padding:0 0.3em; color:#AAA;',
			mbLoadingOpacity: 0.85,
			mbpartialStyle: 'opacity:0.5; font-style:italic; text-decoration: line-through yellow 2.1px;'
		},
		_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 ( !_articleRX ) {
				// prepare has not been run yet.
				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;
		
		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 {
			
            if(mw.util.isIPAddress(item.user)) {
				user.class = 'ip-blocked-temp';
				user.blTime = inHours ( parseTS( user.data.expiry ) - parseTS( user.data.timestamp ) );
			}
				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';
			} 
			var blTime = user.blTime.replace('infinity', mbInfinity);
			user.message = _config.mbTooltip
				.replace( '$1', user.partial )
				.replace( '$2', blTime )
				.replace( '$3', user.data.by )
				.replace( '$4', user.data.reason );
			// 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, {
			mbTooltip: window.mbTooltip,
			mbTempStyle: window.mbTempStyle,
			mbipTempStyle: window.mbipTempStyle,
			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)
		var specialcontri = mw.config.get( 'wgPageContentLanguage');
		if (specialcontri === 'de'){
			var specialcontri = 'Spezial:Beiträge';
		}
		else if (specialcontri === 'nl'){ var specialcontri = 'Speciaal:Bijdragen';}
		else if (specialcontri === 'it'){ var specialcontri = 'Speciale:Contributi';}
		else if (specialcontri === 'ru'){ var specialcontri = 'Служебная:Вклад';}
		else if (specialcontri === 'ja'){ var specialcontri = '特別:投稿記録';}
		else {var specialcontri = 'Special:Contributions';}
		

		_userTitleRX = new RegExp( '^'
			+ '(' + _userNS.join( '|' )
			+ '|'+ specialcontri + '\\/'
			+ ')'
			+ '([^\\/#]+)$', '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 .ip-blocked-temp {'   + _config.mbipTempStyle + '}\
			.mediawiki .user-blocked-indef {'  + _config.mbIndefStyle + '}\
			.mediawiki .user-blocked-tipbox {' + _config.mbTipBoxStyle + '}\
			.mediawiki .user-blocked-partial {' + _config.mbpartialStyle + '}\
		' );
	}
	
	// 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();
	
					mw.hook( 'wikipage.content' ).add( markBlocked );
			  		mw.hook( 'global.userlinks' ).add( markBlocked );
				
			} );
	}
} );