// === MODULE TEXT READER === class TextReaderGame { constructor(options) { this.container = options.container; this.content = options.content; this.onScoreUpdate = options.onScoreUpdate || (() => {}); this.onGameEnd = options.onGameEnd || (() => {}); // Reader state this.currentTextIndex = 0; this.currentSentenceIndex = 0; this.isRunning = false; // Reading data this.texts = this.extractTexts(this.content); this.sentencesFromContent = this.extractSentences(this.content); this.vocabulary = this.extractVocabulary(this.content); this.currentText = null; this.sentences = []; this.showingFullText = false; this.init(); } init() { // Check that we have texts, sentences, or vocabulary if ((!this.texts || this.texts.length === 0) && (!this.sentencesFromContent || this.sentencesFromContent.length === 0) && (!this.vocabulary || this.vocabulary.length === 0)) { logSh('No texts, sentences, or vocabulary available for Text Reader', 'ERROR'); this.showInitError(); return; } this.createReaderInterface(); this.setupEventListeners(); this.loadText(); } showInitError() { this.container.innerHTML = `

❌ Error loading

This content doesn't contain texts, sentences, or vocabulary compatible with Text Reader.

The reader needs content with ultra-modular format (original_language/user_language).

`; } extractTexts(content) { let texts = []; logSh('πŸ“– Extracting texts from:', content?.name || 'content', 'INFO'); // Priority 1: Use raw module content (simple format) if (content.rawContent) { logSh('πŸ“¦ Using raw module content', 'INFO'); return this.extractTextsFromRaw(content.rawContent); } // Priority 2: Ultra-modular format (texts array) - ONLY format supported if (content.texts && Array.isArray(content.texts)) { logSh('✨ Ultra-modular format detected (texts array)', 'INFO'); texts = content.texts .filter(text => text && text.original_language && text.user_language) .map(text => ({ id: text.id || `text_${Date.now()}_${Math.random()}`, title: text.title || text.id || 'Text', original: text.original_language, translation: text.user_language })); logSh(`✨ ${texts.length} texts extracted from ultra-modular format`, 'INFO'); } return this.finalizeTexts(texts); } extractTextsFromRaw(rawContent) { logSh('πŸ”§ Extracting from raw content:', rawContent.name || 'Module', 'INFO'); let texts = []; // Ultra-modular format (texts array) - ONLY format supported if (rawContent.texts && Array.isArray(rawContent.texts)) { texts = rawContent.texts .filter(text => text && text.original_language && text.user_language) .map(text => ({ id: text.id || `text_${Date.now()}_${Math.random()}`, title: text.title || text.id || 'Text', original: text.original_language, translation: text.user_language })); logSh(`✨ ${texts.length} texts extracted from ultra-modular format`, 'INFO'); } return this.finalizeTexts(texts); } finalizeTexts(texts) { // Validation and cleanup texts = texts.filter(text => text && typeof text.original === 'string' && text.original.trim() !== '' && typeof text.translation === 'string' && text.translation.trim() !== '' ); if (texts.length === 0) { logSh('❌ No valid texts found', 'ERROR'); // Demo texts as fallback texts = [ { id: "demo_text_1", title: "Demo Text", original: "This is a demo text. It has multiple sentences. Each sentence will be displayed one by one. You can navigate using the buttons below.", translation: "Ceci est un texte de dΓ©monstration. Il contient plusieurs phrases. Chaque phrase sera affichΓ©e une par une. Vous pouvez naviguer avec les boutons ci-dessous." } ]; logSh('🚨 Using demo texts', 'WARN'); } logSh(`βœ… Text Reader: ${texts.length} texts finalized`, 'INFO'); return texts; } extractSentences(content) { let sentences = []; logSh('πŸ“ Extracting sentences from:', content?.name || 'content', 'INFO'); // Priority 1: Use raw module content if (content.rawContent) { logSh('πŸ“¦ Using raw module content for sentences', 'INFO'); return this.extractSentencesFromRaw(content.rawContent); } // Priority 2: Ultra-modular format (sentences array) - ONLY format supported if (content.sentences && Array.isArray(content.sentences)) { logSh('✨ Ultra-modular format detected (sentences array)', 'INFO'); sentences = content.sentences .filter(sentence => sentence && sentence.original_language && sentence.user_language) .map(sentence => ({ id: sentence.id || `sentence_${Date.now()}_${Math.random()}`, original: sentence.original_language, translation: sentence.user_language })); logSh(`✨ ${sentences.length} sentences extracted from ultra-modular format`, 'INFO'); } return this.finalizeSentences(sentences); } extractSentencesFromRaw(rawContent) { logSh('πŸ”§ Extracting sentences from raw content:', rawContent.name || 'Module', 'INFO'); let sentences = []; // Ultra-modular format (sentences array) - ONLY format supported if (rawContent.sentences && Array.isArray(rawContent.sentences)) { sentences = rawContent.sentences .filter(sentence => sentence && sentence.original_language && sentence.user_language) .map(sentence => ({ id: sentence.id || `sentence_${Date.now()}_${Math.random()}`, original: sentence.original_language, translation: sentence.user_language })); logSh(`✨ ${sentences.length} sentences extracted from ultra-modular format`, 'INFO'); } return this.finalizeSentences(sentences); } finalizeSentences(sentences) { // Validation and cleanup sentences = sentences.filter(sentence => sentence && typeof sentence.original === 'string' && sentence.original.trim() !== '' && typeof sentence.translation === 'string' && sentence.translation.trim() !== '' ); logSh(`βœ… Text Reader: ${sentences.length} sentences finalized`, 'INFO'); return sentences; } extractVocabulary(content) { let vocabulary = []; logSh('πŸ” Extracting vocabulary from:', content?.name || 'content', 'INFO'); // Priority 1: Use raw module content (simple format) if (content.rawContent) { logSh('πŸ“¦ Using raw module content for vocabulary', 'INFO'); return this.extractVocabularyFromRaw(content.rawContent); } // Priority 2: Ultra-modular format (vocabulary object) - ONLY format supported if (content.vocabulary && typeof content.vocabulary === 'object' && !Array.isArray(content.vocabulary)) { logSh('✨ Ultra-modular format detected (vocabulary object)', 'INFO'); vocabulary = Object.entries(content.vocabulary).map(([word, data]) => { // Support ultra-modular format ONLY if (typeof data === 'object' && data.user_language) { return { original: word, // ClΓ© = original_language translation: data.user_language, type: data.type || 'unknown' }; } return null; }).filter(item => item !== null); logSh(`✨ ${vocabulary.length} vocabulary words extracted from ultra-modular format`, 'INFO'); } return this.finalizeVocabulary(vocabulary); } extractVocabularyFromRaw(rawContent) { logSh('πŸ”§ Extracting vocabulary from raw content:', rawContent.name || 'Module', 'INFO'); let vocabulary = []; // Ultra-modular format (vocabulary object) - ONLY format supported if (rawContent.vocabulary && typeof rawContent.vocabulary === 'object' && !Array.isArray(rawContent.vocabulary)) { vocabulary = Object.entries(rawContent.vocabulary).map(([word, data]) => { // Support ultra-modular format ONLY if (typeof data === 'object' && data.user_language) { return { original: word, // ClΓ© = original_language translation: data.user_language, type: data.type || 'unknown' }; } return null; }).filter(item => item !== null); logSh(`✨ ${vocabulary.length} vocabulary words extracted from ultra-modular format`, 'INFO'); } return this.finalizeVocabulary(vocabulary); } finalizeVocabulary(vocabulary) { // Validation and cleanup vocabulary = vocabulary.filter(word => word && typeof word.original === 'string' && word.original.trim() !== '' && typeof word.translation === 'string' && word.translation.trim() !== '' ); logSh(`βœ… Text Reader: ${vocabulary.length} vocabulary words finalized`, 'INFO'); return vocabulary; } createReaderInterface() { this.container.innerHTML = `
1 / 1
Use Next/Previous buttons to navigate through sentences
`; } setupEventListeners() { document.getElementById('next-sentence-btn').addEventListener('click', () => this.nextSentence()); document.getElementById('prev-sentence-btn').addEventListener('click', () => this.prevSentence()); document.getElementById('show-full-btn').addEventListener('click', () => this.showFullText()); document.getElementById('back-to-reading-btn').addEventListener('click', () => this.backToReading()); document.getElementById('text-selector').addEventListener('change', (e) => this.selectText(parseInt(e.target.value))); // Keyboard navigation document.addEventListener('keydown', (e) => { if (!this.isRunning) return; if (this.showingFullText) { if (e.key === 'Escape') this.backToReading(); } else { if (e.key === 'ArrowLeft') this.prevSentence(); else if (e.key === 'ArrowRight') this.nextSentence(); else if (e.key === 'Enter' || e.key === ' ') this.showFullText(); } }); } start() { logSh('πŸ“– Text Reader: Starting', 'INFO'); this.isRunning = true; } restart() { logSh('πŸ”„ Text Reader: Restarting', 'INFO'); this.reset(); this.start(); } reset() { this.currentTextIndex = 0; this.currentSentenceIndex = 0; this.isRunning = false; this.showingFullText = false; this.loadText(); } loadText() { // Use texts if available, otherwise use individual sentences, then vocabulary if (this.texts && this.texts.length > 0) { if (this.currentTextIndex >= this.texts.length) { this.currentTextIndex = 0; } this.currentText = this.texts[this.currentTextIndex]; this.sentences = this.splitIntoSentences(this.currentText.original); this.currentSentenceIndex = 0; this.showingFullText = false; this.populateTextSelector(); } else if (this.sentencesFromContent && this.sentencesFromContent.length > 0) { // Use individual sentences as "mini-texts" this.texts = this.sentencesFromContent.map((sentence, index) => ({ id: sentence.id || `sentence_text_${index}`, title: `Sentence ${index + 1}`, original: sentence.original, translation: sentence.translation })); this.currentText = this.texts[0]; this.sentences = [this.currentText.original]; // Single sentence this.currentSentenceIndex = 0; this.showingFullText = false; this.populateTextSelector(); } else if (this.vocabulary && this.vocabulary.length > 0) { // Use vocabulary words as "mini-texts" this.texts = this.vocabulary.map((word, index) => ({ id: `vocab_text_${index}`, title: `Word: ${word.original}`, original: word.original, translation: word.translation })); this.currentText = this.texts[0]; this.sentences = [this.currentText.original]; // Single word this.currentSentenceIndex = 0; this.showingFullText = false; this.populateTextSelector(); } this.updateDisplay(); this.updateUI(); } populateTextSelector() { const selector = document.getElementById('text-selector'); selector.innerHTML = ''; this.texts.forEach((text, index) => { const option = document.createElement('option'); option.value = index; option.textContent = text.title || `Text ${index + 1}`; if (index === this.currentTextIndex) { option.selected = true; } selector.appendChild(option); }); } selectText(textIndex) { if (textIndex >= 0 && textIndex < this.texts.length) { this.currentTextIndex = textIndex; this.currentText = this.texts[this.currentTextIndex]; this.sentences = this.splitIntoSentences(this.currentText.original); this.currentSentenceIndex = 0; // Always go back to sentence reading when changing text if (this.showingFullText) { this.backToReading(); } else { this.updateDisplay(); this.updateUI(); } this.showFeedback(`Switched to: ${this.currentText.title}`, 'info'); } } splitIntoSentences(text) { // Split by periods, exclamation marks, and question marks // Keep the punctuation with the sentence const sentences = text.split(/(?<=[.!?])\s+/) .filter(sentence => sentence.trim() !== '') .map(sentence => sentence.trim()); return sentences.length > 0 ? sentences : [text]; } nextSentence() { if (this.currentSentenceIndex < this.sentences.length - 1) { this.currentSentenceIndex++; this.updateDisplay(); this.updateUI(); } else { // End of sentences, show full text automatically this.showFullText(); } } prevSentence() { if (this.currentSentenceIndex > 0) { this.currentSentenceIndex--; this.updateDisplay(); this.updateUI(); } } showFullText() { this.showingFullText = true; document.getElementById('sentence-display').style.display = 'none'; document.getElementById('full-text-display').style.display = 'block'; document.getElementById('full-text-display').innerHTML = `

Original:

${this.currentText.original}

Translation:

${this.currentText.translation}

`; // Show full text navigation controls document.querySelector('.reader-controls').style.display = 'none'; document.getElementById('full-text-navigation').style.display = 'flex'; this.showFeedback('Full text displayed. Use dropdown to change text.', 'info'); } backToReading() { this.showingFullText = false; document.getElementById('sentence-display').style.display = 'block'; document.getElementById('full-text-display').style.display = 'none'; // Show sentence navigation controls document.querySelector('.reader-controls').style.display = 'flex'; document.getElementById('full-text-navigation').style.display = 'none'; this.updateDisplay(); this.updateUI(); this.showFeedback('Back to sentence-by-sentence reading.', 'info'); } // Text navigation methods removed - using dropdown instead updateDisplay() { if (this.showingFullText) return; const sentenceDisplay = document.getElementById('sentence-display'); const currentSentence = this.sentences[this.currentSentenceIndex]; sentenceDisplay.innerHTML = `
${currentSentence}
`; } updateUI() { // Update counters document.getElementById('sentence-counter').textContent = `${this.currentSentenceIndex + 1} / ${this.sentences.length}`; // Update button states document.getElementById('prev-sentence-btn').disabled = this.currentSentenceIndex === 0; document.getElementById('next-sentence-btn').disabled = false; document.getElementById('next-sentence-btn').textContent = this.currentSentenceIndex === this.sentences.length - 1 ? 'Full Text β†’' : 'Next β†’'; } // updateTextNavigation method removed - using dropdown instead showFeedback(message, type = 'info') { const feedbackArea = document.getElementById('feedback-area'); feedbackArea.innerHTML = `
${message}
`; } destroy() { this.isRunning = false; this.container.innerHTML = ''; } } // Module registration window.GameModules = window.GameModules || {}; window.GameModules.TextReader = TextReaderGame;