В четверг, 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);
    }
})();