В четверг, 7 мая, около 16 часов (MSK) регистратор заморозил домен «cyclowiki.org» без уведомления владельцев. Сайт недоступен из большинства стран. Правление изучает возможности решения проблемы.
MediaWiki:Gadget-shiftrefs.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.
/**
* Сдвиг всех примечаний на знаки препинания (оптимизированная версия)
* Автор оригинала: [[wikipedia:ru:User:Ignatus]]
* Оптимизация: март 2026 — Циклопедия
*
* Функциональность:
* - Поиск групп сносок (элементы с классом reference)
* - Приклеивание предшествующих знаков препинания к сноскам
* - Обработка последующих знаков препинания
* - Сохранение точного позиционирования через отрицательные margin'ы
*/
(function() {
// Проверка условий запуска
if (typeof wgNamespaceNumber === 'undefined' || wgNamespaceNumber < 0 || !wgUserName) {
return;
}
/* Поиск самого глубокого текстового узла (рекурсивно) */
function getDeepestTextNode(node) {
return node.lastChild ? getDeepestTextNode(node.lastChild) : node;
}
/* Получение чистого текста элемента без HTML-тегов */
function getPlainText(element) {
return element.textContent || element.innerText || '';
}
/* Основная функция обработки */
function processReferences() {
// Поиск контейнера статьи
const container = document.getElementById('wikiPreview') ||
document.getElementById('bodyContent') ||
document.getElementById('mw_contentholder') ||
document.getElementById('article');
if (!container) return;
// Регулярные выражения для знаков препинания
const leadingPunctuationRegex = /^([.,…]*)([\s\S]*?)$/;
const trailingPunctuationRegex = /^([\s\S]*?)([.,…]*)$/;
// Получаем все элементы, которые могут содержать сноски
const elements = container.getElementsByTagName('*');
// Функция для обработки одного элемента
function processElement(element, callback) {
const childNodes = element.childNodes;
const nodesToProcess = [];
// Собираем группы сносок за один проход
for (let i = 0; i < childNodes.length; i++) {
const node = childNodes[i];
// Проверяем, является ли узел элементом с классом reference
if (node.nodeType === 1 && /(^|\s)reference(\s|$)/i.test(node.className)) {
nodesToProcess.push({
index: i,
node: node
});
}
}
// Обрабатываем группы последовательных сносок
let groupStart = -1;
let groupNodes = [];
for (let i = 0; i < nodesToProcess.length; i++) {
const current = nodesToProcess[i];
const prev = i > 0 ? nodesToProcess[i-1] : null;
// Проверяем последовательность
if (prev && current.index === prev.index + 1) {
if (groupStart === -1) {
groupStart = i-1;
groupNodes = [prev.node, current.node];
} else {
groupNodes.push(current.node);
}
} else {
// Обрабатываем накопленную группу
if (groupNodes.length > 0) {
processReferenceGroup(element, groupNodes, groupStart > 0 ? nodesToProcess[groupStart-1] : null, callback);
groupNodes = [];
groupStart = -1;
}
}
}
// Обрабатываем последнюю группу
if (groupNodes.length > 0) {
processReferenceGroup(element, groupNodes, groupStart > 0 ? nodesToProcess[groupStart-1] : null, callback);
}
}
/* Обработка группы последовательных сносок */
function processReferenceGroup(container, refNodes, prevNodeInfo, callback) {
// Создаем контейнер для группы
const groupSpan = document.createElement('span');
groupSpan.style.whiteSpace = 'nowrap';
// Перемещаем все сноски в группу
refNodes.forEach(node => {
groupSpan.appendChild(node);
});
// Вставляем группу обратно в DOM
const firstRefIndex = Array.from(container.childNodes).indexOf(refNodes[0]);
container.insertBefore(groupSpan, container.childNodes[firstRefIndex]);
let accumulatedWidth = 0;
// Обработка предыдущего узла (знаки препинания слева)
if (firstRefIndex > 0) {
const prevNode = container.childNodes[firstRefIndex - 1];
if (prevNode.nodeType === 3) { // Текстовый узел
const match = /^([\s\S]*\s)?(\S*?)([.,…]*)?$/.exec(prevNode.nodeValue);
if (match) {
prevNode.nodeValue = match[1] || '';
const punctuationSpan = document.createElement('span');
const textNode = document.createTextNode(match[3] || match[2]);
punctuationSpan.appendChild(textNode);
groupSpan.insertBefore(punctuationSpan, groupSpan.firstChild);
if (match[3]) {
// Отложенный расчет ширины
setTimeout(() => {
punctuationSpan.style.marginRight = '-' + punctuationSpan.offsetWidth + 'px';
textNode.nodeValue = match[2] + textNode.nodeValue;
}, 0);
}
accumulatedWidth = punctuationSpan.offsetWidth || 0;
}
} else if (prevNode.nodeType === 1) { // Element
const lastTextNode = getDeepestTextNode(prevNode);
const match = trailingPunctuationRegex.exec(lastTextNode.nodeValue || '');
if (match && match[2]) {
lastTextNode.nodeValue = match[1];
const punctuationSpan = document.createElement('span');
punctuationSpan.appendChild(document.createTextNode(match[2]));
lastTextNode.parentNode.appendChild(punctuationSpan);
setTimeout(() => {
const width = punctuationSpan.offsetWidth;
punctuationSpan.style.marginRight = '-' + width + 'px';
}, 0);
accumulatedWidth = punctuationSpan.offsetWidth || 0;
}
// Проверяем возможность включения предыдущего элемента
const prevStyle = prevNode.style.whiteSpace;
if (/nowrap|pre$/.test(prevStyle) || !/\s/.test(getPlainText(prevNode))) {
groupSpan.insertBefore(prevNode, groupSpan.firstChild);
setTimeout(() => {
accumulatedWidth += (prevNode.offsetWidth || 0);
}, 0);
}
}
}
// Обработка следующего узла (знаки препинания справа)
const nextNode = groupSpan.nextSibling;
if (nextNode && nextNode.nodeType === 3) {
const match = leadingPunctuationRegex.exec(nextNode.nodeValue);
if (match && match[1]) {
nextNode.nodeValue = match[2];
const punctuationSpan = document.createElement('span');
punctuationSpan.appendChild(document.createTextNode(match[1]));
setTimeout(() => {
const groupWidth = groupSpan.offsetWidth;
const punctWidth = punctuationSpan.offsetWidth;
groupSpan.appendChild(punctuationSpan);
if (groupSpan.lastChild.previousSibling) {
groupSpan.lastChild.previousSibling.style.marginRight =
(accumulatedWidth - groupWidth) + 'px';
}
punctuationSpan.style.marginRight =
(groupWidth - accumulatedWidth - punctWidth) + 'px';
}, 0);
}
}
if (callback) callback();
}
// Обрабатываем элементы с оптимизацией производительности
const elementList = Array.from(elements);
let currentIndex = 0;
const BATCH_SIZE = 10; // Обрабатываем по 10 элементов за раз
function processBatch() {
const startTime = performance.now();
while (currentIndex < elementList.length &&
(performance.now() - startTime) < 50) { // Не более 50мс за раз
processElement(elementList[currentIndex]);
currentIndex++;
}
if (currentIndex < elementList.length) {
// Используем requestIdleCallback для фоновой обработки
if (window.requestIdleCallback) {
requestIdleCallback(processBatch, { timeout: 100 });
} else {
setTimeout(processBatch, 50);
}
}
}
// Запускаем обработку
if (elementList.length > 0) {
if (window.requestIdleCallback) {
requestIdleCallback(processBatch, { timeout: 100 });
} else {
setTimeout(processBatch, 50);
}
}
}
// Запускаем скрипт после загрузки страницы
if (window.addEventListener) {
window.addEventListener('load', processReferences);
} else {
addOnloadHook(processReferences);
}
})();