MediaWiki:Gadget-speechSynthewiki.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.
/** Speech Synthewiki — гаджет озвучки статей браузером юзера   **/
/*  Бетаверсия 0.2 — надо много поработать над озвучкой формул   */
/*                   надо решить глюк кнопки в нулевой секции    */
/*  Рабочей базой служил движок Mediawiki 1.39.1                 */
/*  Альтернатива [[wikipedia:ru:MediaWiki:Gadget-yandex-tts.js]] */
/*  Автор — Урахара из Циклопедии, лицензия — CC BY-SA 4.0       */

/*****************************************************************/
   // ИСПОЛЬЗОВАНИЕ
   // Возле каждого раздела статьи появляется ссылка «озвучить»
   // Нажимаешь → читает весь текст под этим заголовком
   // Если хочешь остановить — жмёшь «стоп» (кнопка меняется)
   // Когда раздел заканчивается — воспроизведение останавливается
/*****************************************************************/

(function() {
var currentUtterance = null;
var currentButton = null;

var onClick = function(event) {
    event.preventDefault();
    var a = event.target;
    var header = $(a).parent().parent();
    
    // Если нажали на ту же кнопку, которая сейчас воспроизводится - останавливаем
    if (currentButton === a && window.speechSynthesis.speaking) {
        window.speechSynthesis.cancel();
        $(a).text('озвучить');
        currentButton = null;
        currentUtterance = null;
        return;
    }
    
    function sanitizeText(text) {
            var div = document.createElement('div');
            div.textContent = text;
            return div.textContent;
        }


    var text = '';
    var next = header.next();
    while (next.length > 0) {
        if (next.find(".mw-editsection").length !== 0) break;
        var cloned = next.clone();
        
        // Удаляем все элементы с файлами и изображениями
        cloned.find('.thumb, .thumbinner, .thumbcaption, .magnify, .floatnone, .floatleft, .floatright').remove();
        cloned.find('img').remove(); // Удаляем сами изображения
        cloned.find('a.image, a.internal').remove(); // Удаляем ссылки на файлы
        
        // Удаляем ссылки на файлы в тексте (Файл:, Image:, File:)
        cloned.contents().each(function() {
            if (this.nodeType === 3) { // текстовый узел
                var node = $(this);
                var text = node.text();
                // Удаляем строки с "Файл:", "File:", "Image:" и подобными
                if (text.match(/(Файл|File|Image|Изображение|Медиа):/i)) {
                    node.remove();
                }
            } else if (this.nodeType === 1) { // элемент
                var $this = $(this);
                // Проверяем ссылки на файлы
                if ($this.is('a') && $this.attr('href') && $this.attr('href').match(/\.(jpg|jpeg|png|gif|bmp|svg|webp|ogg|mp3|wav|pdf|djvu)$/i)) {
                    $this.remove();
                }
                // Проверяем классы, связанные с файлами
                if ($this.hasClass('image') || $this.hasClass('internal') || $this.attr('href') && $this.attr('href').includes('Файл:')) {
                    $this.remove();
                }
            }
        });
        
        // Удаляем галереи изображений
        cloned.find('ul.gallery, div.gallery, li.gallerybox, div.thumb').remove();
        
        // Удаляем блоки с подписями к изображениям
        cloned.find('.thumbcaption, .gallerycaption').remove();
        
        cloned.find("sup.reference").remove();
        cloned.find(".metadata").remove();
        cloned.find(".noprint").remove();
        
        // Воспроизведение формул
        cloned.find('math').each(function() {
            var formulaText = '';

            // Обработка дробей
            $(this).find('mfrac').each(function() {
                var numerator = $(this).children().first().text().trim();
                var denominator = $(this).children().last().text().trim();
                formulaText += ' дробь: ' + numerator + ' на ' + denominator + ' ';
                $(this).remove();
            });

            // Обработка верхних индексов (степеней)
            $(this).find('msup').each(function() {
                var base = $(this).children().first().text().trim();
                var exponent = $(this).children().last().text().trim();
                
                if (exponent === '2') formulaText += base + ' в квадрате ';
                else if (exponent === '3') formulaText += base + ' в кубе ';
                else formulaText += base + ' в степени ' + exponent + ' ';
                
                $(this).remove();
            });

            // Обработка нижних индексов
            $(this).find('msub').each(function() {
                var base = $(this).children().first().text().trim();
                var subscript = $(this).children().last().text().trim();
                formulaText += base + ' с индексом ' + subscript + ' ';
                $(this).remove();
            });

            // Обработка корней
            $(this).find('msqrt').each(function() {
                var underRoot = $(this).text().trim();
                formulaText += ' корень из ' + underRoot + ' ';
                $(this).remove();
            });

            // Замена спецсимволов на читаемые слова
            var allText = $(this).text().trim();
            
            allText = allText
                .replace(/×/g, ' умножить ')
                .replace(/÷/g, ' разделить ')
                .replace(/≠/g, ' не равно ')
                .replace(/≈/g, ' приблизительно равно ')
                .replace(/≤/g, ' меньше или равно ')
                .replace(/≥/g, ' больше или равно ')
                .replace(/±/g, ' плюс минус ')
                .replace(/∞/g, ' бесконечность ')
                .replace(/√/g, ' корень из ')
                .replace(/π/g, ' пи ')
                .replace(/∑/g, ' сумма ')
                .replace(/∏/g, ' произведение ')
                .replace(/∫/g, ' интеграл ')
                .replace(/∂/g, ' частная производная ')
                .replace(/∇/g, ' набла ')
                .replace(/∈/g, ' принадлежит ')
                .replace(/∉/g, ' не принадлежит ')
                .replace(/∩/g, ' пересечение ')
                .replace(/∪/g, ' объединение ')
                .replace(/⊂/g, ' подмножество ')
                .replace(/⊃/g, ' надмножество ');
            
            formulaText += allText;

            if (formulaText) {
                $(this).replaceWith(document.createTextNode(' формула: ' + formulaText + ' '));
            }
        });
        
        text += sanitizeText(cloned.text());
        text += "\n";
        next = next.next();
    }
    
    if (window.speechSynthesis) {
        // Останавливаем предыдущее воспроизведение, если оно было
        if (window.speechSynthesis.speaking) {
            window.speechSynthesis.cancel();
            if (currentButton) {
                $(currentButton).text('озвучить');
            }
        }
        
        currentUtterance = new SpeechSynthesisUtterance(text);
        currentUtterance.lang = 'ru-RU';
        currentUtterance.rate = 1.0;
        currentUtterance.pitch = 1.0;
        currentUtterance.volume = 1.0;
        
        var voices = window.speechSynthesis.getVoices();
        var russianVoice = voices.find(function(voice) {
            return voice.lang.includes('ru');
        });
        
        if (russianVoice) {
            currentUtterance.voice = russianVoice;
        }
        
        currentUtterance.onstart = function() {
            $(a).text('стоп');
            currentButton = a;
        };
        
        // ВАЖНО: когда воспроизведение заканчивается естественным путём
        currentUtterance.onend = function() {
            // Возвращаем кнопку в исходное состояние
            $(a).text('озвучить');
            currentButton = null;
            currentUtterance = null;
        };
        
        // Обработка ошибок - тоже возвращаем кнопку
        currentUtterance.onerror = function(event) {
            // Не сбрасываем кнопку при прерывании (когда остановили вручную)
            if (event.error !== 'interrupted') {
                $(a).text('озвучить');
                currentButton = null;
                currentUtterance = null;
            }
        };
        
        if (voices.length === 0) {
            window.speechSynthesis.onvoiceschanged = function() {
                var updatedVoices = window.speechSynthesis.getVoices();
                var ruVoice = updatedVoices.find(function(v) {
                    return v.lang.includes('ru');
                });
                if (ruVoice) {
                    currentUtterance.voice = ruVoice;
                }
                window.speechSynthesis.speak(currentUtterance);
            };
        } else {
            window.speechSynthesis.speak(currentUtterance);
        }
    } else {
        alert('Ваш браузер не поддерживает синтез речи');
    }
};

$(document).ready(function() {
    $(".mw-editsection").each(function(i, item) {
        if (!$(item).prev().hasClass('cyclo-tts-start')) {
            var a = $('<span class="mw-editsection cyclo-tts-start">[<a href="#" style="cursor:pointer;">озвучить</a>]</span> ');
            $(item).before(a);
            a.click(onClick);
        }
    });
});

})();