В четверг, 7 мая, около 16 часов (MSK) регистратор заморозил домен «cyclowiki.org» без уведомления владельцев. Сайт недоступен из большинства стран. Правление изучает возможности решения проблемы.
MediaWiki:Gadget-markblocked.js
Перейти к навигации
Перейти к поиску
Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
- Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
- Internet Explorer / Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
- Opera: Нажмите Ctrl+F5.
'use strict';
(function() {
// ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========
// Экранирование HTML-спецсимволов
function escapeHTML(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Безопасная обработка CSS-значений
function sanitizeCSS(val, fallback) {
if (typeof val !== 'string') return fallback;
// Удаляем всё, кроме безопасных CSS-символов
val = val.replace(/[{};'"\\]|url\(|expression\(|javascript:/gi, '');
return val || fallback;
}
// Преобразует timestamp в объект 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 zz(v) {
if (v <= 9) v = '0' + v;
return v;
}
// Преобразует миллисекунды в читаемую строку
function inHours(ms) {
var mm = Math.floor(ms / 60000);
if (!mm) return Math.floor(ms / 1000) + 'с';
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) : '') + 'д';
else return hh + ':' + zz(mm);
}
// ========== САНИТИЗАЦИЯ НАСТРОЕК ==========
// Стили для временно заблокированных
var tempStyle = sanitizeCSS(window.mbTempStyle, 'opacity: 0.7; text-decoration: line-through');
// Стили для бессрочно заблокированных
var indefStyle = sanitizeCSS(window.mbIndefStyle, 'opacity: 0.4; font-style: italic; text-decoration: line-through');
// Стили для всплывающей подсказки
var tipBoxStyle = sanitizeCSS(window.mbTipBoxStyle, 'font-size:smaller; background:#FFFFF0; border:1px solid #FEA; padding:0 0.3em; color:#AAA');
// Добавляем стили на страницу
mw.util.addCSS(
'.user-blocked-temp{' + tempStyle + '}' +
'.user-blocked-indef{' + indefStyle + '}' +
'.user-blocked-tipbox{' + tipBoxStyle + '}'
);
// Шаблон подсказки (с вырезанием HTML-тегов и экранированием)
var rawTip = window.mbTooltip || '; заблокирован ($1) администратором $2: $3 ($4 назад)';
var mbTooltip = rawTip.replace(/<[^>]*>/g, '');
// ========== ОСНОВНАЯ ФУНКЦИЯ ==========
var isMarkingInProgress = false;
var currentRequest = null;
function markBlocked(container) {
// Предотвращаем повторный запуск
if (isMarkingInProgress) return;
isMarkingInProgress = true;
// Получаем переменные MediaWiki
var wgNamespaceIds = mw.config.get('wgNamespaceIds');
var wgArticlePath = mw.config.get('wgArticlePath');
var wgScript = mw.config.get('wgScript');
// Определяем, где искать ссылки
var contentLinks = container
? $(container).find('a')
: mw.util.$content.find('a').add('#ca-nstab-user a');
// Получаем все псевдонимы для пространств "Участник:" и "Обсуждение участника:"
var userNS = [];
for (var ns in wgNamespaceIds) {
if (wgNamespaceIds[ns] == 2 || wgNamespaceIds[ns] == 3) {
userNS.push(ns.replace(/_/g, ' ') + ':');
}
}
// Регулярка для заголовков
var userTitleRX = new RegExp(
'^(' + userNS.join('|') + '|Служебная:Вклад\\/|Special:Contributions\\/)' + '([^\\/#]+)$',
'i'
);
// Регулярки для разбора URL ссылок
var articleRX = new RegExp('^' + wgArticlePath.replace('$1', '') + '([^#]+)');
var scriptRX = new RegExp('^' + wgScript + '\\?title=([^#&]+)');
var userLinks = {};
var url, ma, pgTitle;
// Находим все ссылки на участников
contentLinks.each(function(i, lnk) {
url = $(lnk).attr('href');
if (!url || url.charAt(0) != '/') return;
else if ((ma = articleRX.exec(url))) pgTitle = ma[1];
else if ((ma = scriptRX.exec(url))) pgTitle = ma[1];
else return;
pgTitle = decodeURIComponent(pgTitle).replace(/_/g, ' ');
var user = userTitleRX.exec(pgTitle);
if (!user) return;
user = user[2];
$(lnk).addClass('userlink');
if (!userLinks[user]) userLinks[user] = [];
userLinks[user].push(lnk);
});
// Преобразуем объект в массив имён участников
var users = [];
for (var u in userLinks) users.push(u);
if (users.length == 0) {
isMarkingInProgress = false;
return;
}
// Индикатор загрузки
var wgServerTime, apiRequests = 0, hasErrors = false;
var waitingCSS = mw.util.addCSS('a.userlink {opacity:' + (window.mbLoadingOpacity || 0.85) + '}');
// Отправляем запросы к API (пакетами по 50 имён)
while (users.length > 0) {
apiRequests++;
var currentUsers = users.splice(0, 50);
currentRequest = $.ajax({
url: mw.util.wikiScript('api') + '?format=json&action=query',
type: 'POST',
data: {
list: 'blocks',
bklimit: 100,
bkusers: currentUsers.join('|'),
bkprop: 'user|by|timestamp|expiry|reason'
},
dataType: 'json'
})
.done(function(resp, textStatus, xhr) {
markLinks(resp, xhr);
})
.fail(function(jqXHR, textStatus, errorThrown) {
hasErrors = true;
console.warn('MarkBlocked: API request failed for users:', currentUsers.join(', '), textStatus, errorThrown);
checkCompletion();
});
}
// ========== ОБРАБОТКА ОТВЕТА API ==========
function markLinks(resp, xhr) {
// Получаем серверное время из заголовка ответа
if (!wgServerTime && xhr) {
wgServerTime = new Date(xhr.getResponseHeader('Date'));
}
var list, blk, tip, links, lnk, clss, blTime;
if (!resp || !(list = resp.query) || !(list = list.blocks)) {
checkCompletion();
return;
}
// Обрабатываем каждый блок
for (var i = 0; i < list.length; i++) {
blk = list[i];
// Определяем тип блокировки
if (/^in/.test(blk.expiry)) {
clss = 'user-blocked-indef';
blTime = escapeHTML(blk.expiry);
} else {
clss = 'user-blocked-temp';
var expiryTime = parseTS(blk.expiry);
var timestampTime = parseTS(blk.timestamp);
blTime = inHours(expiryTime - timestampTime);
}
// Формируем подсказку с экранированием
tip = mbTooltip
.replace('$1', blTime)
.replace('$2', escapeHTML(blk.by))
.replace('$3', escapeHTML(blk.reason))
.replace('$4', wgServerTime ? inHours(wgServerTime - parseTS(blk.timestamp)) : 'неизвестно');
// Применяем стили и подсказку ко всем ссылкам заблокированного участника
links = userLinks[blk.user];
if (!links) continue;
for (var k = 0; k < links.length; k++) {
lnk = $(links[k]);
// Предотвращаем дублирование
if (lnk.hasClass('user-blocked-processed')) continue;
lnk.addClass('user-blocked-processed');
lnk.addClass(clss);
if (window.mbTipBox) {
$('<span class="user-blocked-tipbox">#</span>')
.attr('title', tip)
.insertBefore(lnk);
} else {
var currentTitle = lnk.attr('title') || '';
lnk.attr('title', currentTitle + (currentTitle ? ' ' : '') + tip);
}
}
}
checkCompletion();
}
// Проверка завершения всех запросов
function checkCompletion() {
apiRequests--;
if (apiRequests <= 0) {
// Убираем индикатор загрузки
if (waitingCSS) {
waitingCSS.disabled = true;
}
// Удаляем кнопку ручного запуска, если есть
var showBlocksLink = document.getElementById('ca-showblocks');
if (showBlocksLink && showBlocksLink.parentNode) {
showBlocksLink.parentNode.removeChild(showBlocksLink);
}
isMarkingInProgress = false;
currentRequest = null;
if (hasErrors) {
console.warn('MarkBlocked: Some API requests failed. Results may be incomplete.');
}
}
}
}
// ========== ЗАПУСК ==========
var wgAction = mw.config.get('wgAction');
var wgNamespaceNumber = mw.config.get('wgNamespaceNumber');
switch (wgAction) {
case 'edit':
case 'submit':
// Не запускаем в режиме редактирования
break;
case 'view':
// Не запускаем в основном пространстве при просмотре
if (wgNamespaceNumber == 0) break;
// В остальных случаях — продолжаем
default:
// 'history', 'purge' и всё остальное
$(function() {
if (window.mbNoAutoStart) {
// Добавляем ссылку в меню для ручного запуска
mw.util.addPortletLink('p-cactions', '#', 'Показать блокировки', 'ca-showblocks');
$('#ca-showblocks').on('click', function(e) {
e.preventDefault();
markBlocked();
});
} else {
// Автоматический запуск
markBlocked();
}
});
}
})();