MediaWiki:Gadget-HighlightUnpatrolledLinks.js
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();
} );