[go: nahoru, domu]

JS-код ниже относится к гаджету «Выделить другим цветом ссылки на непатрулированные статьи» (править описание). Его использует около 100 учётных записей.

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

/* <nowiki>
 * HighlightUnpatrolledLinks.js
 * Based on [[ru:User:Ignatus/patlinkshl.js]]
 * Local and global variables are lowerCamelCase
 * Selectors and DOM nodes are CamelCase
 * Local variables start with _
 */
$( function() {
	if ( !window.HighlightUnpatrolledLinks ) {
		window.HighlightUnpatrolledLinks = {};
	}

	/*
	 * Global settings and variables
	 */
	HighlightUnpatrolledLinks.defaultPrefs = {
		name: 'HighlightUnpatrolledLinks',
		// Регулярное выражение имени страницы, где гаджет работает <везде>, например, new RegExp("^(Проект|Портал):.*/Новые (страницы|статьи)")
		pagere: null,
		// Не обрабатывать ссылки, содержащие изображения
		noimg: false,
		// Использовать классы подсветки в СН (проверено для скина Vector)
		stdclasses: true
	};
	
	HighlightUnpatrolledLinks.prefs = $.extend({}, HighlightUnpatrolledLinks.defaultPrefs, HighlightUnpatrolledLinks.prefs || {});

	/*
	 * Local settings and variables
	 */
	var _c = mw.config.get( [
		'wgScriptPath',
		'wgServer',
		'wgNamespaceNumber',
		'wgArticlePath',
		'wgPageName',
		'wgAction',
		'wgUserName',
		'wgUserGroups'
	] );
	
	var _isAllowed = (
		_c.wgNamespaceNumber >= 0 && 
		_c.wgUserName &&
		( _c.wgUserGroups.indexOf( 'editor' ) > -1 || _c.wgUserGroups.indexOf( 'sysop' ) > -1 ) &&
		( !HighlightUnpatrolledLinks.prefs.pagere || HighlightUnpatrolledLinks.prefs.pagere.test( _c.wgPageName ) ) && 
		['submit', 'view', 'historysubmit', 'purge'].indexOf( _c.wgAction ) > -1
	);
	
	// Здесь должен быть актуальный список патрулируемых пространств имён
	var _patnss = {0 : true, 6 : true, 10 : true, 14 : true, 100 : true, 828 : true};
	
	// Класс для ссылок на статью с непроверенной последней версией
	var	_className = ( HighlightUnpatrolledLinks.prefs.stdclasses ? 'flaggedrevs-pending' : 'unpat-link' );
	
	// Класс для ссылок на непатрулировавшуюся статью
	var	_className2 = ( HighlightUnpatrolledLinks.prefs.stdclasses ? 'flaggedrevs-unreviewed' : 'totally-unpat-link' );
	
	var	_titleAppend = ' (не патрулировано)';
	
	var	_titleAppend2 = ' (не патрулировалось)';
	
	var	_titleAppend3 = ' (непатрулированное перенаправление)';
	
	var	_titleAppend4 = ' (не патрулировавшееся перенаправление)';

	var	_queryUrlPreview = [_c.wgScriptPath, 'api.php?action=query&format=json&prop=flagged&indexpageids'].join('/');
	
	// {"название страницы" : ['класс CSS', 'что дописать к названию ссылки'], ... }
	var	_titles = {};
	
	// Список редиректов по названию
	var	_redirects = {};
	
	// Доступный всем функциям список элементов-ссылок
	var	_links;
	
	// Получать страницы из DOM (иначе запрашвать через generator=links)
	var	_linksfrompg;
	
	// Cчётчик помечаемых страниц
	var	_count;
	
	// Хранилище части имён страниц к запросу
	var	_ta;
	
	// Счётчик ожидаемых запросов, когда вернётся к 0, помечаем
	var	_previewQueryCount;

	/*
	 * Local functions
	 */
	 var addCSS = function( css ) {
		var styleElem = document.createElement( 'style' );
		styleElem.appendChild( document.createTextNode( css ) );
		document.getElementsByTagName( 'head' )[0].appendChild( styleElem );
	};
	
	//Конструктор запроса к БД, используется для предпросмотра и редиректов
	function PreviewQuery( titles, wut ) {
		//Размечать будем, когда придут все ответы
		_previewQueryCount++;
		// We have to keep the titles in memory in case we get a query-continue
		// Редиректы запрашиваем по pageids, остальное - по titles
		this.data = 'titles='+ titles.join( '|' );
		this.doQuery( _queryUrlPreview + wut );
	};
	
	PreviewQuery.prototype.doQuery = function( url ) {
		var q = this;
		$.ajax( {
			url: url,
			method: 'POST',
			contentType: 'application/x-www-form-urlencoded',
			data: q.data,
			success: function( data, textStatus, jqXHR ) {
				q.resultArrived( data );
			}
		} );
	};

	//Обработка запроса в предпросмотре
	PreviewQuery.prototype.resultArrived = function( res ) {
		storeTitles( res );
		if ( res && res['continue'] ) {
			// Не уложились в лимит
			var c = res['continue'];
			var url = _queryUrlPreview + 
				'&gplcontinue=' + encodeURIComponent( c.gplcontinue ) +
				'&continue=' + encodeURIComponent( c['continue'] );
			// Если продолжаем запрос ссылок из БД
			if( !_linksfrompg ) {
				url += '&generator=links&gpllimit=max&gplnamespace=' + _ta.join('|');
			}
			// Если продолжаем запрос редиректов
			if( res.query && res.query.redirects ) {
				url += '&redirects';
			}
			// Посылаем продолженный запрос по тем же страницам в this.data
			this.doQuery( url );
		} else {
			// Все данные по текущему набору получены, уменьшаем счётчик запросов
			if( !--_previewQueryCount ) {
				// Все запросы выполнены
				if( !( res && res.query && res.query.redirects ) ) {
					// Обрабатываем редиректы, если мы не этим занимались
					var rids = [];
					for( var rid in _redirects ) {
						rids.push( rid );
						if( rids.length < 50 ) {
							continue;
						}
						// Запрашиваем редиректы
						new PreviewQuery( rids, '&redirects' );
						rids = [];
					}
					if( rids.length ) {
						new PreviewQuery( rids, '&redirects' );
					}
				}
				// Если редиректы не запущены, размечаем
				if( !_previewQueryCount ) {
					markLinks();
				}
			}
		}
	};
	
	// Получаем список ссылок в предпросмотре и делаем по ним запросы
	function execute( $content ) {
		_links = getLinks( $content );
		_count = 0;
		_previewQueryCount = 0;
		if ( !_links ) {
			return;
		}
		var m, m1;
		_ta = [];
		_linksfrompg = _c.wgAction !== 'view' || _c.wgNamespaceNumber === 14;
		// В режиме просмотра получать ссылки быстрее через generator=links, если это не категория
		var unique = {};
		var rxEscape = function(s) { 
			return s.replace( /([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1' );
		};
		// Для мобильных версий 
		var siteRegex = new RegExp(
			rxEscape( _c.wgServer ).replace( "\\.wiki","(\\.m)?\\.wiki" ) +
			rxEscape( _c.wgArticlePath.replace( /\$1/, '' ) ) + '([^#]*)'
		);
		// Здесь должен быть актуальный список патрулируемых п. и.
		var patnsregex = new RegExp( "^(|Шаблон|Файл|Модуль|Категория|Портал)$" );
		// We only care for some ns pages, so we can filter out the most common cases to save some requests
		// Помещаем в локальную titles базу заголовков
		for ( var i = 0; i < _links.length; i++ ) { 
			if (
				!_links[i].title || 
				!( m = _links[i].href.match( siteRegex ) ) || 
				( m1 = m[2].match( '^([^:]+):' ) ) && 
				!m1[1].match( patnsregex ) || unique[m[2]] || 
				HighlightUnpatrolledLinks.prefs.noimg && 
				$( _links[i] ).find( 'img' ).length
			) {
				continue;
			}
			// Avoid requesting same title multiple times
			unique[m[2]] = true;
			// Запоминаем, что это редирект
			if( $( _links[i] ).hasClass( 'mw-redirect' ) ) {
				_redirects[_links[i].title] = true;
			}
			if( _linksfrompg ){
				// Avoid normalization of titles // Надо ли?
				_ta.push( m[2].replace( /_/g, '%20' ) );
				// Лимит заглавий 50
				if ( _ta.length < 50 ) {
					continue;
				}
				new PreviewQuery( _ta, '' );
				_ta =[];
			} else {
				// Сигнал, что что-то есть
				_linksfrompg = null;
			}
		}
		if ( _ta.length ) {
			new PreviewQuery( _ta, '' );
		} else if( _linksfrompg === null ) {
			for( i in _patnss ) {
				_ta.push( String(i) );
			}
			new PreviewQuery( [_c.wgPageName], '&generator=links&gpllimit=max&gplnamespace=' + _ta.join( '|' ) );
		}
	};

	function getLinks( el ) {
		return el && el.find( 'a' );
	};
	
	// Собственно, отобразить пометки
	function markLinks() {
		if ( !_count || !_links ) {
			 return;
		}
		for ( var i = 0; i < _links.length; i++ ) {
			// Do not mess with images and user-specified objects
			if (
				/image/.test( _links[i].className) || 
				HighlightUnpatrolledLinks.prefs.noimg && $( _links[i] ).find( 'img' ).length
			) {
				continue;
			}
			var tpl = _titles[_links[i].title];
			if ( !tpl ) {
				continue;
			}
			// Помещаем внутренность ссылки в цветной span
			var $span = $( '<span>' )
				.addClass( tpl[0] )
				.attr( 'title', _links[i].title + tpl[1] );
			$( _links[i] ).wrapInner( $span );
			if( tpl[1].indexOf( 'перенапр' ) != -1 ) {
				_links[i].href += ( _links[i].href.indexOf( '?' ) === -1 ? '?' : '&' ) + 'redirect=no';
			}
			// Если это непатрулированное перенаправление, то меняем ссылку на статью на ссылку на редирект
		}
	};
	
	// Запись в titles и redirects результатов запроса к API
	function storeTitles( res ) {
		if ( !res || !res.query || !res.query.pageids ) {
			return;
		}
		var q = res.query;
		var pids = q.pageids;
		var i;
		// Массив-накопитель редиректов
		var ra;
		// Если редирект не патрулирован, показываем его статус и направляем ссылку без перехода.
		// Если патрулирован, то потом запросим и покажем статус статьи.
		if( q.redirects ) {
			// Если вызываем для набора перенаправлений
			// [{from:'редирект',to:'куда'},...]
			var rdlist = q.redirects; 
			// Здесь переводим в ассоциативную базу
			ra = {};
			for ( i = 0; i < rdlist.length; i++) {
				// {статья:перенаправление, ...}
				ra[rdlist[i].to] = rdlist[i].from;
			}
		}
		for ( i = 0; i < pids.length; i++ ) {
			var page = q.pages[pids[i]];
			var isrd = page.title in _redirects;
			// Не обрабатываем чужие п. и.
			if ( page.missing === '' || !_patnss[page.ns] ) {
				continue;
			}
			if ( page.flagged && !page.flagged.pending_since ) {
				// Патрулировано
				continue; 
			} else if ( isrd ) {
				// Непатрулированное перенаправление не надо запрашивать
				delete _redirects[page.title];
			}
			_count++;
			_titles[ 'redirects' in q ? ra[page.title] : page.title ] = page.flagged ? 
				[ _className,  ( isrd ? _titleAppend3 : _titleAppend  ) ] : 
				[ _className2, ( isrd ? _titleAppend4 : _titleAppend2 ) ];
		}
	};

	/*
	 * Initialising
	 */
	HighlightUnpatrolledLinks.Init = function() {
		if( !_isAllowed ) {
			return;
		}
		addCSS('\
			.unpat-link { background-color:#FFFF80; padding-left:1px; padding-right:1px; padding-top:1px; padding-bottom:2px; }\
			.totally-unpat-link { background-color:#FFB080; padding-left:1px; padding-right:1px; padding-top:1px; padding-bottom:2px; }\
		');
		mw.hook('wikipage.content').add( execute );
	};

	/* </nowiki>
	 * Starting point
	 */
	HighlightUnpatrolledLinks.Init();
} );