// T5 Text Simplifier - JavaScript Functionality class TextSimplifier { constructor() { // Since we're running the Flask server locally, we'll use the local URL this.apiUrl = 'http://localhost:5000'; this.isDyslexiaMode = false; this.isProcessing = false; this.speechSynthesis = window.speechSynthesis; this.currentUtterance = null; this.isManuallyStopping = false; this.setupElements(); this.setupEventListeners(); this.initializeTooltip(); this.checkSpeechSupport(); } setupElements() { // Input elements this.inputText = document.getElementById('inputText'); this.clearInput = document.getElementById('clearInput'); this.copyInput = document.getElementById('copyInput'); // Output elements this.outputText = document.getElementById('outputText'); this.clearOutput = document.getElementById('clearOutput'); this.copyOutput = document.getElementById('copyOutput'); this.downloadOutput = document.getElementById('downloadOutput'); // Control elements this.simplifyBtn = document.getElementById('simplifyBtn'); this.dyslexiaToggle = document.getElementById('dyslexiaToggle'); this.ttsInput = document.getElementById('ttsInput'); this.ttsOutput = document.getElementById('ttsOutput'); // Info elements this.infoBtn = document.querySelector('.info-btn'); this.infoTooltip = document.getElementById('infoTooltip'); this.closeTooltip = document.querySelector('.close-tooltip'); // Status message this.statusMessage = document.getElementById('statusMessage'); } setupEventListeners() { // Main functionality this.simplifyBtn.addEventListener('click', () => this.simplifyText()); // Temporarily disable keydown listener to test // this.inputText.addEventListener('keydown', (e) => this.handleInputKeydown(e)); // Remove the input event listeners that might be interfering // this.inputText.addEventListener('input', (e) => { // // Allow normal text input without any interference // return true; // }); // this.outputText.addEventListener('input', (e) => { // // Allow normal text input without any interference // return true; // }); // Dyslexia toggle this.dyslexiaToggle.addEventListener('click', () => this.toggleDyslexiaMode()); // Text-to-Speech this.ttsInput.addEventListener('click', () => this.handleTTSButtonClick('input')); this.ttsOutput.addEventListener('click', () => this.handleTTSButtonClick('output')); // Action buttons this.clearInput.addEventListener('click', () => this.clearText(this.inputText)); this.clearOutput.addEventListener('click', () => this.clearText(this.outputText)); this.copyInput.addEventListener('click', () => this.copyToClipboard(this.inputText.value, 'Input text')); this.copyOutput.addEventListener('click', () => this.copyToClipboard(this.outputText.value, 'Simplified text')); this.downloadOutput.addEventListener('click', () => this.downloadText()); // Info tooltip this.infoBtn.addEventListener('click', () => this.showTooltip()); this.closeTooltip.addEventListener('click', () => this.hideTooltip()); // Close tooltip on outside click - temporarily disabled to test // document.addEventListener('click', (e) => { // if (!this.infoTooltip.contains(e.target) && !this.infoBtn.contains(e.target)) { // this.hideTooltip(); // } // }); // Keyboard shortcuts - only enable speech stop shortcut document.addEventListener('keydown', (e) => this.handleGlobalKeydown(e)); // Stop speech when page is hidden document.addEventListener('visibilitychange', () => { if (document.hidden) { this.stopSpeech(); } }); } initializeTooltip() { // Add backdrop for tooltip const backdrop = document.createElement('div'); backdrop.className = 'tooltip-backdrop'; backdrop.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 999; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; `; document.body.appendChild(backdrop); this.tooltipBackdrop = backdrop; } checkSpeechSupport() { if (!this.speechSynthesis) { this.ttsInput.disabled = true; this.ttsOutput.disabled = true; this.ttsInput.title = 'Text-to-speech not supported in this browser'; this.ttsOutput.title = 'Text-to-speech not supported in this browser'; } else { // Log available voices for debugging this.logAvailableVoices(); } } logAvailableVoices() { // Wait for voices to load (they might not be available immediately) const loadVoices = () => { const voices = this.speechSynthesis.getVoices(); if (voices.length > 0) { console.log('Available TTS voices:'); voices.forEach((voice, index) => { console.log(`${index + 1}. ${voice.name} (${voice.lang}) - ${voice.default ? 'Default' : 'Available'}`); }); } else { // Try again after a short delay setTimeout(loadVoices, 100); } }; loadVoices(); } async simplifyText() { const text = this.inputText.value.trim(); if (!text) { this.showStatus('Please enter some text to simplify.', 'warning'); return; } if (this.isProcessing) { return; } this.setProcessingState(true); this.showStatus('Simplifying your text...', 'info'); try { const response = await fetch(`${this.apiUrl}/simplify`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ text }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.error) { throw new Error(data.error); } if (data.simplified_text) { this.outputText.value = data.simplified_text; this.showStatus('Text simplified successfully!', 'success'); this.outputText.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else { throw new Error('No simplified text received from server'); } } catch (error) { console.error('Error simplifying text:', error); this.showStatus(`Failed to simplify text: ${error.message}`, 'error'); } finally { this.setProcessingState(false); } } setProcessingState(isProcessing) { this.isProcessing = isProcessing; this.simplifyBtn.disabled = isProcessing; if (isProcessing) { this.simplifyBtn.classList.add('loading'); } else { this.simplifyBtn.classList.remove('loading'); } } toggleDyslexiaMode() { this.isDyslexiaMode = !this.isDyslexiaMode; document.body.classList.toggle('dyslexia-friendly', this.isDyslexiaMode); this.dyslexiaToggle.setAttribute('aria-pressed', this.isDyslexiaMode); const status = this.isDyslexiaMode ? 'enabled' : 'disabled'; this.showStatus(`Dyslexia-friendly formatting ${status}`, 'info'); // Update button text const buttonText = this.dyslexiaToggle.querySelector('span'); buttonText.textContent = this.isDyslexiaMode ? 'Dyslexia-Friendly ✓' : 'Dyslexia-Friendly'; } handleTTSButtonClick(type) { // Check if speech is currently playing if (this.speechSynthesis.speaking) { this.stopSpeech(); } else { const text = type === 'input' ? this.inputText.value : this.outputText.value; this.speakText(text, type); } } speakText(text, type) { if (!this.speechSynthesis) { this.showStatus('Text-to-speech is not supported in this browser', 'warning'); return; } if (!text.trim()) { this.showStatus(`No ${type} text to read`, 'warning'); return; } // Stop any current speech this.stopSpeech(); this.currentUtterance = new SpeechSynthesisUtterance(text); // Configure speech settings for more natural sound this.currentUtterance.rate = 0.85; // Slightly slower for better comprehension this.currentUtterance.pitch = 0.95; // Slightly lower pitch for warmth this.currentUtterance.volume = 0.9; // Higher volume for clarity // Try to use the most natural voice available const voices = this.speechSynthesis.getVoices(); let selectedVoice = null; // Priority order for voice selection (most natural first) const voicePreferences = [ // Google Cloud voices (most natural) voice => voice.name.includes('Google') && voice.name.includes('Neural'), voice => voice.name.includes('Google') && voice.name.includes('Wavenet'), voice => voice.name.includes('Google'), // Microsoft voices voice => voice.name.includes('Microsoft') && voice.name.includes('Neural'), voice => voice.name.includes('Microsoft'), // Amazon Polly voices voice => voice.name.includes('Amazon') && voice.name.includes('Neural'), voice => voice.name.includes('Amazon'), // Apple voices voice => voice.name.includes('Samantha') || voice.name.includes('Alex'), voice => voice.name.includes('Apple'), // Other natural voices voice => voice.name.includes('Natural') || voice.name.includes('Enhanced'), voice => voice.name.includes('Premium'), voice => voice.name.includes('Neural'), // Fallback to any English voice voice => voice.lang.startsWith('en') ]; // Find the best available voice for (const preference of voicePreferences) { selectedVoice = voices.find(voice => voice.lang.startsWith('en') && preference(voice) ); if (selectedVoice) break; } if (selectedVoice) { this.currentUtterance.voice = selectedVoice; console.log('Using voice:', selectedVoice.name); } else { console.log('Using default voice'); } // Event handlers this.currentUtterance.onstart = () => { this.showStatus(`Reading ${type} text...`, 'info'); this.updateTTSButton(type, true); }; this.currentUtterance.onend = () => { this.showStatus(`Finished reading ${type} text`, 'success'); this.updateTTSButton(type, false); this.currentUtterance = null; this.isManuallyStopping = false; // Reset the flag }; this.currentUtterance.onerror = (event) => { console.error('Speech synthesis error:', event); // Only show error message if we're not manually stopping if (!this.isManuallyStopping) { this.showStatus('Error reading text', 'error'); } this.updateTTSButton(type, false); this.currentUtterance = null; this.isManuallyStopping = false; // Reset the flag }; this.speechSynthesis.speak(this.currentUtterance); } stopSpeech() { if (this.speechSynthesis.speaking) { this.isManuallyStopping = true; this.speechSynthesis.cancel(); this.showStatus('Stopped reading aloud', 'info'); } this.currentUtterance = null; this.updateTTSButton('input', false); this.updateTTSButton('output', false); } updateTTSButton(type, isSpeaking) { const button = type === 'input' ? this.ttsInput : this.ttsOutput; const icon = button.querySelector('i'); const text = button.querySelector('span'); if (isSpeaking) { icon.className = 'fas fa-stop'; text.textContent = 'Stop'; button.style.backgroundColor = 'var(--error-color)'; button.style.color = 'white'; } else { icon.className = 'fas fa-volume-up'; text.textContent = `Read ${type === 'input' ? 'Input' : 'Output'}`; button.style.backgroundColor = ''; button.style.color = ''; } } clearText(textElement) { textElement.value = ''; // Don't automatically focus - let user decide where to focus this.showStatus('Text cleared', 'info'); } async copyToClipboard(text, type) { if (!text.trim()) { this.showStatus(`No ${type.toLowerCase()} to copy`, 'warning'); return; } try { await navigator.clipboard.writeText(text); this.showStatus(`${type} copied to clipboard!`, 'success'); } catch (error) { console.error('Failed to copy text:', error); // Fallback for older browsers this.fallbackCopyToClipboard(text, type); } } fallbackCopyToClipboard(text, type) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); this.showStatus(`${type} copied to clipboard!`, 'success'); } catch (error) { console.error('Fallback copy failed:', error); this.showStatus('Failed to copy text', 'error'); } document.body.removeChild(textArea); } downloadText() { const text = this.outputText.value.trim(); if (!text) { this.showStatus('No simplified text to download', 'warning'); return; } const blob = new Blob([text], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `simplified-text-${new Date().toISOString().split('T')[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showStatus('Text downloaded successfully!', 'success'); } showTooltip() { this.infoTooltip.classList.add('show'); this.tooltipBackdrop.style.pointerEvents = 'auto'; this.tooltipBackdrop.style.opacity = '1'; this.infoTooltip.setAttribute('aria-hidden', 'false'); // Focus management this.closeTooltip.focus(); } hideTooltip() { this.infoTooltip.classList.remove('show'); this.tooltipBackdrop.style.pointerEvents = 'none'; this.tooltipBackdrop.style.opacity = '0'; this.infoTooltip.setAttribute('aria-hidden', 'true'); // Return focus to info button this.infoBtn.focus(); } showStatus(message, type = 'info') { this.statusMessage.textContent = message; this.statusMessage.className = `status-message ${type} show`; // Auto-hide after 4 seconds setTimeout(() => { this.statusMessage.classList.remove('show'); }, 4000); } handleInputKeydown(e) { // Ctrl/Cmd + Enter to simplify if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); this.simplifyText(); } // Don't interfere with normal typing return true; } handleGlobalKeydown(e) { // Only handle speech stop shortcut to avoid interfering with text input // Ctrl/Cmd + Shift + S to stop speech if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'S') { e.preventDefault(); this.stopSpeech(); } // Escape to close tooltip (only if tooltip is open) if (e.key === 'Escape' && this.infoTooltip.classList.contains('show')) { this.hideTooltip(); } } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new TextSimplifier(); // Show welcome message setTimeout(() => { const statusMessage = document.getElementById('statusMessage'); statusMessage.textContent = 'Welcome! Enter your text and click "Simplify Text" to get started.'; statusMessage.className = 'status-message success show'; setTimeout(() => { statusMessage.classList.remove('show'); }, 5000); }, 1000); }); // Service Worker registration for offline functionality (optional) if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/static/js/sw.js') .then(registration => { console.log('SW registered: ', registration); }) .catch(registrationError => { console.log('SW registration failed: ', registrationError); }); }); }