PageSourceSearch

https://revistasagarana.com.br/wp-content/plugins/leitor/js/leitor.js?ver=1.0

js revistasagarana.com.br collected 2026-10-03 00:30:11 UTC 12,446 bytes, 298 lines download raw bytes

1jQuery(document).ready(function($) {
2    var playButton = $('#leitor-de-texto-play-button');
3    var pauseButton = $('#leitor-de-texto-pause-button');
4    var stopButton = $('#leitor-de-texto-stop-button');
5    var rateControl = $('.leitor-de-texto-rate-control');
6    var rateInput = $('#leitor-de-texto-rate');
7    var rateValueSpan = $('#leitor-de-texto-rate-value');
8    var readingTimeSpan = $('#leitor-de-texto-reading-time'); // Novo elemento para o tempo de leitura
9
10    var isSpeaking = false;
11    var isPaused = false;
12    var currentUtterance = null;
13    var selectedVoice = null;
14    var speechQueue = [];
15    var fullContentText = ''; // Para armazenar o texto completo após a limpeza
16
17    // Define a velocidade inicial no input e no span
18    rateInput.val(leitorDeTextoData.defaultRate);
19    rateValueSpan.text(leitorDeTextoData.defaultRate);
20
21    // Evento para atualizar o valor da velocidade no span e no utterance (se estiver falando)
22    rateInput.on('input', function() {
23        rateValueSpan.text($(this).val());
24        if (currentUtterance) {
25            currentUtterance.rate = parseFloat($(this).val());
26        }
27    });
28
29    // Verifica se a API SpeechSynthesis é suportada pelo navegador
30    if (!('speechSynthesis' in window)) {
31        console.warn('Leitor de Texto: Seu navegador não suporta a API de Text-to-Speech. Os botões serão ocultados.');
32        $('.leitor-de-texto-controls').hide(); // Esconde todos os controles se não houver suporte
33        return; // Sai da função se não houver suporte
34    }
35
36    // --- Funções Auxiliares ---
37
38    // Função para obter a voz feminina em português do Brasil
39    function getBrazilianFemaleVoice() {
40        var voices = window.speechSynthesis.getVoices();
41        var femaleVoice = null;
42        var defaultLang = leitorDeTextoData.language.replace('_', '-'); // Ex: 'pt-BR'
43
44        // 1. Tenta encontrar uma voz explicitamente feminina em pt-BR
45        for (var i = 0; i < voices.length; i++) {
46            if (voices[i].lang === defaultLang && voices[i].name.toLowerCase().includes('female')) {
47                femaleVoice = voices[i];
48                break;
49            }
50        }
51
52        // 2. Se não encontrar uma feminina, tenta qualquer voz em pt-BR
53        if (!femaleVoice) {
54            for (var i = 0; i < voices.length; i++) {
55                if (voices[i].lang === defaultLang) {
56                    femaleVoice = voices[i];
57                    break;
58                }
59            }
60        }
61
62        // 3. Se ainda não encontrar pt-BR, tenta qualquer voz que comece com 'pt'
63        if (!femaleVoice) {
64             var langPrefix = defaultLang.substring(0, 2);
65             for (var i = 0; i < voices.length; i++) {
66                if (voices[i].lang.startsWith(langPrefix)) {
67                    femaleVoice = voices[i];
68                    break;
69                }
70            }
71        }
72        
73        // 4. Como último recurso, retorna a primeira voz disponível
74        if (!femaleVoice && voices.length > 0) {
75             femaleVoice = voices[0];
76        }
77
78        return femaleVoice;
79    }
80
81    // Função para atualizar o estado dos botões
82    function updateButtonState(state) {
83        if (state === 'playing') {
84            playButton.hide();
85            pauseButton.show();
86            stopButton.show();
87            rateControl.show();
88        } else if (state === 'paused') {
89            playButton.html('<i class="fas fa-play"></i> ' + leitorDeTextoData.buttonText); // Volta para play
90            playButton.show();
91            pauseButton.hide();
92            stopButton.show();
93            rateControl.show();
94        } else { // 'stopped' ou 'initial'
95            playButton.html('<i class="fas fa-play"></i> ' + leitorDeTextoData.buttonText);
96            playButton.show();
97            pauseButton.hide();
98            stopButton.hide();
99            rateControl.hide();
100        }
101    }
102
103    // Função para processar o texto e adicionar à fila de fala
104    function processAndQueueText(rawText) {
105        speechQueue = [];
106        // Divide o texto em sentenças ou parágrafos para melhor controle da leitura
107        var paragraphs = rawText.split(/[\n\r]+/); // Divide por quebras de linha
108        paragraphs.forEach(function(p) {
109            var sentences = p.match(/[^.!?]+[.!?]|\S+/g); // Tenta dividir por sentenças
110            if (sentences) {
111                sentences.forEach(function(s) {
112                    s = s.trim();
113                    if (s.length > 0) {
114                        speechQueue.push(s);
115                    }
116                });
117            } else if (p.trim().length > 0) {
118                speechQueue.push(p.trim());
119            }
120        });
121    }
122
123    // Função para iniciar a próxima parte da fala na fila
124    function speakNextInQueue() {
125        if (speechQueue.length > 0 && !isPaused) {
126            var textToSpeak = speechQueue.shift(); // Pega a próxima parte
127            currentUtterance = new SpeechSynthesisUtterance(textToSpeak);
128
129            if (selectedVoice) {
130                currentUtterance.voice = selectedVoice;
131                currentUtterance.lang = selectedVoice.lang;
132            } else {
133                currentUtterance.lang = leitorDeTextoData.language.replace('_', '-');
134            }
135
136            currentUtterance.rate = parseFloat(rateInput.val());
137            currentUtterance.pitch = parseFloat(leitorDeTextoData.defaultPitch);
138
139            currentUtterance.onend = function() {
140                if (speechQueue.length > 0) {
141                    speakNextInQueue(); // Continua para a próxima parte
142                } else {
143                    // Leitura completa
144                    isSpeaking = false;
145                    isPaused = false;
146                    updateButtonState('stopped');
147                }
148            };
149
150            currentUtterance.onerror = function(event) {
151                console.error('Leitor de Texto: Erro na síntese de voz para a parte:', event.error, event.utterance.text);
152                isSpeaking = false;
153                isPaused = false;
154                updateButtonState('stopped');
155                // Não exibir alert aqui para evitar sobrecarga de popups em erros de partes
156            };
157            
158            window.speechSynthesis.speak(currentUtterance);
159        }
159 else if (speechQueue.length === 0 && isSpeaking) {
160            // Leitura completa (se a fila esvaziou e ainda estava falando)
161            isSpeaking = false;
162            isPaused = false;
163            updateButtonState('stopped');
164        }
165    }
166
167    // Função para calcular e exibir o tempo de leitura
168    function calculateAndDisplayReadingTime(text) {
169        var wordCount = text.split(/\s+/).length; // Conta palavras separadas por espaço
170        var minutes = Math.ceil(wordCount / leitorDeTextoData.wordsPerMinute);
171
172        if (minutes === 0) {
173            readingTimeSpan.text(leitorDeTextoData.lessThanOneMinuteText);
174        } else if (minutes === 1) {
175            readingTimeSpan.text(leitorDeTextoData.readingTimeTextSingular.replace('%d', minutes));
176        } else {
177            readingTimeSpan.text(leitorDeTextoData.readingTimeTextPlural.replace('%d', minutes));
178        }
179    }
180
181    // --- Inicialização da Voz ---
182    // Este evento é disparado quando as vozes do sistema estão prontas.
183    window.speechSynthesis.onvoiceschanged = function() {
184        selectedVoice = getBrazilianFemaleVoice();
185        if (!selectedVoice) {
186            console.warn('Leitor de Texto: Nenhuma voz adequada (feminina pt-BR) encontrada. Usando a voz padrão do navegador ou a primeira disponível.');
187        } else {
188            console.log('Leitor de Texto: Voz selecionada:', selectedVoice.name, selectedVoice.lang);
189        }
190    };
191
192    // Força o carregamento das vozes em navegadores onde 'onvoiceschanged' pode não disparar
193    if (window.speechSynthesis.getVoices().length === 0) {
194        window.speechSynthesis.getVoices(); 
195    } else {
196        selectedVoice = getBrazilianFemaleVoice();
197    }
198
199
200    // --- Extrair e Preparar Conteúdo para Leitura e Tempo de Leitura ---
201    function extractContentAndCalculateTime() {
202        var mainContent = $(leitorDeTextoData.contentSelectors); 
203        
204        if (mainContent.length > 0) {
205            var clonedContent = mainContent.clone();
206            clonedContent.find('button, script, style, noscript, .screen-reader-text, .wp-block-audio, .wp-block-video, .wp-block-embed, .leitor-de-texto-controls, .gallery, header, footer, nav, aside, .wp-block-site-logo, .wp-block-search, .widget_area, .elementor-element-overlay').remove();
207            
208            // Pega o texto de cada elemento correspondente ao seletor e o junta.
209            // Isso é crucial para seletores como '.entry p, .entry h3'
210            var collectedText = [];
211            clonedContent.each(function() {
212                // Tenta pegar o texto de blocos de parágrafos/cabeçalhos diretamente
213                if ($(this).is('p, h1, h2, h3, h4, h5, h6, em, strong, li')) {
214                    collectedText.push($(this).text());
215                } else {
216                    // Para seletores de containers (ex: .entry-content), pega todo o texto de dentro
217                    collectedText.push($(this).text());
218                }
219            });
220
221            fullContentText = collectedText.join(' ').replace(/\s\s+/g, ' ').trim(); 
222            calculateAndDisplayReadingTime(fullContentText);
223
224        } else {
225            console.error('Leitor de Texto: ERRO! Não foi possível encontrar o conteúdo principal para leitura. Verifique os seletores CSS do seu tema no arquivo leitor.php.');
226            // Não exibe alert aqui para não irritar o usuário no carregamento da página
227            readingTimeSpan.text('Conteúdo não encontrado'); // Indica erro no tempo de leitura
228            playButton.prop('disabled', true); // Desabilita o botão se não houver conteúdo
229        }
230
231        if (fullContentText.length === 0) {
232             console.warn('Leitor de Texto: O conteúdo a ser lido está vazio após a filtragem. Nada para ler.');
233             readingTimeSpan.text('N/A'); // Indica que não há texto
234             playButton.prop('disabled', true); // Desabilita o botão se o texto estiver vazio
235        }
236    }
237
238    // --- Event Listeners dos Botões ---
239
240    playButton.on('click', function() {
241        if (!selectedVoice) {
242            console.warn('Leitor de Texto: Nenhuma voz disponível para leitura. Verifique as configurações de voz do seu navegador/sistema operacional.');
243            alert('Não foi possível iniciar a leitura. Por favor, verifique se seu navegador possui vozes de leitura instaladas e ativadas.');
244            return;
245        }
246
247        if (isPaused) {
248            window.speechSynthesis.resume();
249            isPaused = false;
250            isSpeaking = true;
251            updateButtonState('playing');
252            return;
253        }
254
255        if (fullContentText.length === 0) {
256             alert('Não há conteúdo textual para ser lido nesta página.');
257             return;
258        }
259        
260        // Limpa qualquer leitura anterior antes de iniciar uma nova
261        window.speechSynthesis.cancel();
262        isSpeaking = true;
263        isPaused = false;
264        updateButtonState('playing');
265
266        processAndQueueText(fullContentText); // Processa o texto completo
267        speakNextInQueue();
268    });
269
270    pauseButton.on('click', function() {
271        if (isSpeaking && !isPaused) {
272            window.speechSynthesis.pause();
273            isPaused = true;
274            updateButtonState('paused');
275        }
276    });
277
278    stopButton.on('click', function() {
279        if (isSpeaking || isPaused) {
280            window.speechSynthesis.cancel(); // Cancela a leitura
281            isSpeaking = false;
282            isPaused = false;
283            speechQueue = []; // Limpa a fila de fala
284            updateButtonState('stopped');
285        }
286    });
287
288    // Adiciona um listener para quando a janela for fechada ou recarregada
289    $(window).on('beforeunload', function() {
290        if (isSpeaking || isPaused) {
291            window.speechSynthesis.cancel();
292        }
293    });
294
295    // Garante que os botões tenham o estado inicial correto e calcula o tempo de leitura na carga da página
296    updateButtonState('stopped');
297    extractContentAndCalculateTime(); // Chama esta função para calcular o tempo ao carregar a página
298});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.