Adapt text-reader.js to ultra-modular format
- Replace legacy extractTexts() method with ultra-modular support - Add support for texts with original_language/user_language format - Add extractSentences() and extractVocabulary() methods for content flexibility - Support reading individual sentences and vocabulary words as mini-texts - Remove legacy content.content references, use content.original/translation - Update full text display to show both original and translation - Maintain backward compatibility through rawContent fallback - Improve error messages to reflect ultra-modular requirements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cb614a439d
commit
0d5f577f28
@ -7,13 +7,15 @@ class TextReaderGame {
|
|||||||
this.onScoreUpdate = options.onScoreUpdate || (() => {});
|
this.onScoreUpdate = options.onScoreUpdate || (() => {});
|
||||||
this.onGameEnd = options.onGameEnd || (() => {});
|
this.onGameEnd = options.onGameEnd || (() => {});
|
||||||
|
|
||||||
// État du lecteur
|
// Reader state
|
||||||
this.currentTextIndex = 0;
|
this.currentTextIndex = 0;
|
||||||
this.currentSentenceIndex = 0;
|
this.currentSentenceIndex = 0;
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
|
|
||||||
// Données de lecture
|
// Reading data
|
||||||
this.texts = this.extractTexts(this.content);
|
this.texts = this.extractTexts(this.content);
|
||||||
|
this.sentencesFromContent = this.extractSentences(this.content);
|
||||||
|
this.vocabulary = this.extractVocabulary(this.content);
|
||||||
this.currentText = null;
|
this.currentText = null;
|
||||||
this.sentences = [];
|
this.sentences = [];
|
||||||
this.showingFullText = false;
|
this.showingFullText = false;
|
||||||
@ -22,13 +24,15 @@ class TextReaderGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Vérifier que nous avons des textes
|
// Check that we have texts, sentences, or vocabulary
|
||||||
if (!this.texts || this.texts.length === 0) {
|
if ((!this.texts || this.texts.length === 0) &&
|
||||||
logSh('Aucun texte disponible pour Text Reader', 'ERROR');
|
(!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();
|
this.showInitError();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.createReaderInterface();
|
this.createReaderInterface();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.loadText();
|
this.loadText();
|
||||||
@ -38,8 +42,8 @@ class TextReaderGame {
|
|||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
<div class="game-error">
|
<div class="game-error">
|
||||||
<h3>❌ Error loading</h3>
|
<h3>❌ Error loading</h3>
|
||||||
<p>This content doesn't contain texts compatible with Text Reader.</p>
|
<p>This content doesn't contain texts, sentences, or vocabulary compatible with Text Reader.</p>
|
||||||
<p>The reader needs texts to display.</p>
|
<p>The reader needs content with ultra-modular format (original_language/user_language).</p>
|
||||||
<button onclick="AppNavigation.goBack()" class="back-btn">← Back</button>
|
<button onclick="AppNavigation.goBack()" class="back-btn">← Back</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -47,85 +51,208 @@ class TextReaderGame {
|
|||||||
|
|
||||||
extractTexts(content) {
|
extractTexts(content) {
|
||||||
let texts = [];
|
let texts = [];
|
||||||
|
|
||||||
logSh('📖 Extracting texts from:', content?.name || 'content', 'INFO');
|
logSh('📖 Extracting texts from:', content?.name || 'content', 'INFO');
|
||||||
|
|
||||||
// Use raw module content if available
|
// Priority 1: Use raw module content (simple format)
|
||||||
if (content.rawContent) {
|
if (content.rawContent) {
|
||||||
logSh('📦 Using raw module content', 'INFO');
|
logSh('📦 Using raw module content', 'INFO');
|
||||||
return this.extractTextsFromRaw(content.rawContent);
|
return this.extractTextsFromRaw(content.rawContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format with texts array
|
// Priority 2: Ultra-modular format (texts array) - ONLY format supported
|
||||||
if (content.texts && Array.isArray(content.texts)) {
|
if (content.texts && Array.isArray(content.texts)) {
|
||||||
logSh('📝 Texts format detected', 'INFO');
|
logSh('✨ Ultra-modular format detected (texts array)', 'INFO');
|
||||||
texts = content.texts.filter(text =>
|
texts = content.texts
|
||||||
text.content && text.content.trim() !== ''
|
.filter(text => text && text.original_language && text.user_language)
|
||||||
);
|
.map(text => ({
|
||||||
}
|
id: text.id || `text_${Date.now()}_${Math.random()}`,
|
||||||
// Modern format with contentItems
|
title: text.title || text.id || 'Text',
|
||||||
else if (content.contentItems && Array.isArray(content.contentItems)) {
|
original: text.original_language,
|
||||||
logSh('🆕 ContentItems format detected', 'INFO');
|
translation: text.user_language
|
||||||
texts = content.contentItems
|
|
||||||
.filter(item => item.type === 'text' && item.content)
|
|
||||||
.map(item => ({
|
|
||||||
title: item.title || 'Text',
|
|
||||||
content: item.content
|
|
||||||
}));
|
}));
|
||||||
|
logSh(`✨ ${texts.length} texts extracted from ultra-modular format`, 'INFO');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.finalizeTexts(texts);
|
return this.finalizeTexts(texts);
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTextsFromRaw(rawContent) {
|
extractTextsFromRaw(rawContent) {
|
||||||
logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
|
logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
|
||||||
let texts = [];
|
let texts = [];
|
||||||
|
|
||||||
// Simple format (texts array)
|
// Ultra-modular format (texts array) - ONLY format supported
|
||||||
if (rawContent.texts && Array.isArray(rawContent.texts)) {
|
if (rawContent.texts && Array.isArray(rawContent.texts)) {
|
||||||
texts = rawContent.texts.filter(text =>
|
texts = rawContent.texts
|
||||||
text.content && text.content.trim() !== ''
|
.filter(text => text && text.original_language && text.user_language)
|
||||||
);
|
.map(text => ({
|
||||||
logSh(`📝 ${texts.length} texts extracted from texts array`, 'INFO');
|
id: text.id || `text_${Date.now()}_${Math.random()}`,
|
||||||
}
|
title: text.title || text.id || 'Text',
|
||||||
// ContentItems format
|
original: text.original_language,
|
||||||
else if (rawContent.contentItems && Array.isArray(rawContent.contentItems)) {
|
translation: text.user_language
|
||||||
texts = rawContent.contentItems
|
|
||||||
.filter(item => item.type === 'text' && item.content)
|
|
||||||
.map(item => ({
|
|
||||||
title: item.title || 'Text',
|
|
||||||
content: item.content
|
|
||||||
}));
|
}));
|
||||||
logSh(`🆕 ${texts.length} texts extracted from contentItems`, 'INFO');
|
logSh(`✨ ${texts.length} texts extracted from ultra-modular format`, 'INFO');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.finalizeTexts(texts);
|
return this.finalizeTexts(texts);
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizeTexts(texts) {
|
finalizeTexts(texts) {
|
||||||
// Validation and cleanup
|
// Validation and cleanup
|
||||||
texts = texts.filter(text =>
|
texts = texts.filter(text =>
|
||||||
text &&
|
text &&
|
||||||
typeof text.content === 'string' &&
|
typeof text.original === 'string' &&
|
||||||
text.content.trim() !== ''
|
text.original.trim() !== '' &&
|
||||||
|
typeof text.translation === 'string' &&
|
||||||
|
text.translation.trim() !== ''
|
||||||
);
|
);
|
||||||
|
|
||||||
if (texts.length === 0) {
|
if (texts.length === 0) {
|
||||||
logSh('❌ No valid texts found', 'ERROR');
|
logSh('❌ No valid texts found', 'ERROR');
|
||||||
// Demo texts as fallback
|
// Demo texts as fallback
|
||||||
texts = [
|
texts = [
|
||||||
{
|
{
|
||||||
title: "Demo Text",
|
id: "demo_text_1",
|
||||||
content: "This is a demo text. It has multiple sentences. Each sentence will be displayed one by one. You can navigate using the buttons below."
|
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('🚨 Using demo texts', 'WARN');
|
||||||
}
|
}
|
||||||
|
|
||||||
logSh(`✅ Text Reader: ${texts.length} texts finalized`, 'INFO');
|
logSh(`✅ Text Reader: ${texts.length} texts finalized`, 'INFO');
|
||||||
return texts;
|
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() {
|
createReaderInterface() {
|
||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
<div class="text-reader-wrapper">
|
<div class="text-reader-wrapper">
|
||||||
@ -215,16 +342,50 @@ class TextReaderGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadText() {
|
loadText() {
|
||||||
if (this.currentTextIndex >= this.texts.length) {
|
// Use texts if available, otherwise use individual sentences, then vocabulary
|
||||||
this.currentTextIndex = 0;
|
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.currentText = this.texts[this.currentTextIndex];
|
|
||||||
this.sentences = this.splitIntoSentences(this.currentText.content);
|
|
||||||
this.currentSentenceIndex = 0;
|
|
||||||
this.showingFullText = false;
|
|
||||||
|
|
||||||
this.populateTextSelector();
|
|
||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
}
|
}
|
||||||
@ -248,7 +409,7 @@ class TextReaderGame {
|
|||||||
if (textIndex >= 0 && textIndex < this.texts.length) {
|
if (textIndex >= 0 && textIndex < this.texts.length) {
|
||||||
this.currentTextIndex = textIndex;
|
this.currentTextIndex = textIndex;
|
||||||
this.currentText = this.texts[this.currentTextIndex];
|
this.currentText = this.texts[this.currentTextIndex];
|
||||||
this.sentences = this.splitIntoSentences(this.currentText.content);
|
this.sentences = this.splitIntoSentences(this.currentText.original);
|
||||||
this.currentSentenceIndex = 0;
|
this.currentSentenceIndex = 0;
|
||||||
|
|
||||||
// Always go back to sentence reading when changing text
|
// Always go back to sentence reading when changing text
|
||||||
@ -298,7 +459,14 @@ class TextReaderGame {
|
|||||||
document.getElementById('full-text-display').style.display = 'block';
|
document.getElementById('full-text-display').style.display = 'block';
|
||||||
document.getElementById('full-text-display').innerHTML = `
|
document.getElementById('full-text-display').innerHTML = `
|
||||||
<div class="full-text-content">
|
<div class="full-text-content">
|
||||||
<p>${this.currentText.content}</p>
|
<div class="text-original">
|
||||||
|
<h4>Original:</h4>
|
||||||
|
<p>${this.currentText.original}</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-translation">
|
||||||
|
<h4>Translation:</h4>
|
||||||
|
<p>${this.currentText.translation}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user