feat(vocab): integrate fallback core patterns and enhance vocabulary display in VocabLessonView
All checks were successful
Deploy to production / deploy (push) Successful in 2m44s

- Added a new method in VocabService to merge core pattern glosses with fallback patterns, improving vocabulary clarity and consistency.
- Updated VocabLessonView to utilize the merged core patterns, ensuring a comprehensive vocabulary overview for users.
- Refactored vocabulary handling logic to enhance user experience during vocabulary lessons, including improved display of lesson vocabulary.
This commit is contained in:
Torsten Schulz (local)
2026-04-01 09:58:32 +02:00
parent 1328e4983e
commit 6d13965c76
6 changed files with 144 additions and 45 deletions

View File

@@ -0,0 +1,24 @@
export const BISAYA_PHASE1_DIDACTICS = {
'Begrüßungen & Höflichkeit': {
corePatterns: [
{ target: 'Kumusta ka?', gloss: 'Wie geht es dir?' },
{ target: 'Maayong buntag.', gloss: 'Guten Morgen.' },
{ target: 'Maayong adlaw.', gloss: 'Guten Tag.' },
{ target: 'Maayong gabii.', gloss: 'Guten Abend.' },
{ target: 'Maayong gabii, matulog na ta.', gloss: 'Guten Abend, wir legen uns schlafen.' },
{ target: 'Katulog og maayo.', gloss: 'Schlaf gut.' },
{ target: 'Kapoy na ka?', gloss: 'Bist du müde?' },
{ target: 'Matulog na ta.', gloss: 'Lass uns schlafen gehen.' },
{ target: 'Inom sa og tubig.', gloss: 'Trink Wasser.' },
{ target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' },
{ target: 'Tabuni ang imong kaugalingon.', gloss: 'Deck dich zu.' },
{ target: 'Ugma nasad.', gloss: 'Bis morgen wieder.' },
{ target: 'Damgo og nindot.', gloss: 'Träum schön.' },
{ target: 'Amping.', gloss: 'Pass auf dich auf.' },
{ target: 'Babay.', gloss: 'Tschüss.' },
{ target: 'Maayo ko.', gloss: 'Mir geht es gut.' },
{ target: 'Salamat.', gloss: 'Danke.' },
{ target: 'Palihug.', gloss: 'Bitte.' }
]
}
};

View File

@@ -12,6 +12,7 @@ import UserParam from '../models/community/user_param.js';
import { sequelize } from '../utils/sequelize.js'; import { sequelize } from '../utils/sequelize.js';
import { notifyUser } from '../utils/socket.js'; import { notifyUser } from '../utils/socket.js';
import { Op } from 'sequelize'; import { Op } from 'sequelize';
import { BISAYA_PHASE1_DIDACTICS } from '../scripts/bisaya-course-phase1.js';
export default class VocabService { export default class VocabService {
async _getUserByHashedId(hashedUserId) { async _getUserByHashedId(hashedUserId) {
@@ -347,6 +348,27 @@ export default class VocabService {
}); });
} }
_mergeCorePatternGlosses(primaryPatterns = [], fallbackPatterns = []) {
const fallbackByTarget = new Map(
fallbackPatterns
.map((entry) => this._normalizeCorePatternEntry(entry))
.filter(Boolean)
.map((entry) => [this._normalizeLexeme(entry.target), entry.gloss || ''])
);
return primaryPatterns.map((entry) => {
const normalized = this._normalizeCorePatternEntry(entry);
if (!normalized) {
return null;
}
if (normalized.gloss) {
return normalized;
}
const gloss = fallbackByTarget.get(this._normalizeLexeme(normalized.target)) || '';
return gloss ? { ...normalized, gloss } : normalized;
}).filter(Boolean);
}
_normalizeStructuredList(value, keys = ['title', 'text']) { _normalizeStructuredList(value, keys = ['title', 'text']) {
if (!value) return []; if (!value) return [];
if (Array.isArray(value)) { if (Array.isArray(value)) {
@@ -560,9 +582,13 @@ export default class VocabService {
const learningGoals = this._normalizeStringList(plainLesson.learningGoals); const learningGoals = this._normalizeStringList(plainLesson.learningGoals);
const extractedTrainerVocabs = this._extractTrainerVocabsFromExercises(grammarExercises); const extractedTrainerVocabs = this._extractTrainerVocabsFromExercises(grammarExercises);
const corePatterns = this._enrichCorePatternsWithGloss( const phase1FallbackCorePatterns = BISAYA_PHASE1_DIDACTICS[plainLesson.title]?.corePatterns || [];
this._normalizeCorePatternList(plainLesson.corePatterns), const corePatterns = this._mergeCorePatternGlosses(
extractedTrainerVocabs this._enrichCorePatternsWithGloss(
this._normalizeCorePatternList(plainLesson.corePatterns),
extractedTrainerVocabs
),
phase1FallbackCorePatterns
); );
const grammarFocus = this._normalizeStructuredList(plainLesson.grammarFocus, ['title', 'text', 'example']); const grammarFocus = this._normalizeStructuredList(plainLesson.grammarFocus, ['title', 'text', 'example']);
const explicitSpeakingPrompts = this._normalizeStructuredList(plainLesson.speakingPrompts, ['title', 'prompt', 'cue']); const explicitSpeakingPrompts = this._normalizeStructuredList(plainLesson.speakingPrompts, ['title', 'prompt', 'cue']);
@@ -578,9 +604,12 @@ export default class VocabService {
], ],
corePatterns: corePatterns.length > 0 corePatterns: corePatterns.length > 0
? corePatterns ? corePatterns
: this._enrichCorePatternsWithGloss( : this._mergeCorePatternGlosses(
uniquePatterns.slice(0, 5).map((s) => ({ target: String(s || '').trim(), gloss: '' })).filter((p) => p.target), this._enrichCorePatternsWithGloss(
extractedTrainerVocabs uniquePatterns.slice(0, 5).map((s) => ({ target: String(s || '').trim(), gloss: '' })).filter((p) => p.target),
extractedTrainerVocabs
),
phase1FallbackCorePatterns
), ),
grammarFocus: grammarFocus.length > 0 ? grammarFocus : uniqueGrammarExplanations.slice(0, 4), grammarFocus: grammarFocus.length > 0 ? grammarFocus : uniqueGrammarExplanations.slice(0, 4),
speakingPrompts: explicitSpeakingPrompts.length > 0 ? explicitSpeakingPrompts : speakingPrompts.slice(0, 4), speakingPrompts: explicitSpeakingPrompts.length > 0 ? explicitSpeakingPrompts : speakingPrompts.slice(0, 4),

View File

@@ -456,7 +456,9 @@
"vocabPrepStep2": "Gehe die gleichen Begriffe noch einmal durch (aktive Wiederholung, ohne zu üben).", "vocabPrepStep2": "Gehe die gleichen Begriffe noch einmal durch (aktive Wiederholung, ohne zu üben).",
"vocabPrepConfirm2": "Zweite Durchsicht erledigt", "vocabPrepConfirm2": "Zweite Durchsicht erledigt",
"vocabPrepReady": "Du kannst jetzt mit dem Vokabeltrainer starten.", "vocabPrepReady": "Du kannst jetzt mit dem Vokabeltrainer starten.",
"vocabOverviewToggle": "Gesamtübersicht der Begriffe anzeigen",
"vocabTrainerLockedHint": "Bitte bestätige zuerst zwei Lern-Durchgänge bei „Vorbereitung vor dem Vokabeltrainer“.", "vocabTrainerLockedHint": "Bitte bestätige zuerst zwei Lern-Durchgänge bei „Vorbereitung vor dem Vokabeltrainer“.",
"exerciseUnlockHintAfterPrep": "Arbeite zuerst die vorbereiteten Begriffe durch. Danach wird die Kapitel-Prüfung freigeschaltet.",
"speakingTasks": "Sprechaufträge", "speakingTasks": "Sprechaufträge",
"speakingPrompt": "Sprechauftrag", "speakingPrompt": "Sprechauftrag",
"practicalTasks": "Praxisaufgaben", "practicalTasks": "Praxisaufgaben",

View File

@@ -456,7 +456,9 @@
"vocabPrepStep2": "Go through the same items again (active review, not testing yet).", "vocabPrepStep2": "Go through the same items again (active review, not testing yet).",
"vocabPrepConfirm2": "Second pass done", "vocabPrepConfirm2": "Second pass done",
"vocabPrepReady": "You can start the vocabulary trainer now.", "vocabPrepReady": "You can start the vocabulary trainer now.",
"vocabOverviewToggle": "Show full overview of terms",
"vocabTrainerLockedHint": "Please confirm two preparation steps under “Preparation before the vocabulary trainer” first.", "vocabTrainerLockedHint": "Please confirm two preparation steps under “Preparation before the vocabulary trainer” first.",
"exerciseUnlockHintAfterPrep": "Work through the prepared terms first. The chapter test will unlock afterwards.",
"speakingTasks": "Speaking Tasks", "speakingTasks": "Speaking Tasks",
"speakingPrompt": "Speaking Prompt", "speakingPrompt": "Speaking Prompt",
"practicalTasks": "Practical Tasks", "practicalTasks": "Practical Tasks",

View File

@@ -454,7 +454,9 @@
"vocabPrepStep2": "Repasa los mismos elementos otra vez (repaso activo, aún sin practicar).", "vocabPrepStep2": "Repasa los mismos elementos otra vez (repaso activo, aún sin practicar).",
"vocabPrepConfirm2": "Segunda lectura hecha", "vocabPrepConfirm2": "Segunda lectura hecha",
"vocabPrepReady": "Ya puedes iniciar el entrenador de vocabulario.", "vocabPrepReady": "Ya puedes iniciar el entrenador de vocabulario.",
"vocabOverviewToggle": "Mostrar vista general completa de los términos",
"vocabTrainerLockedHint": "Confirma primero los dos pasos de preparación arriba.", "vocabTrainerLockedHint": "Confirma primero los dos pasos de preparación arriba.",
"exerciseUnlockHintAfterPrep": "Primero recorre los términos preparados. Después se desbloqueará la prueba del capítulo.",
"speakingTasks": "Tareas orales", "speakingTasks": "Tareas orales",
"speakingPrompt": "Tarea oral", "speakingPrompt": "Tarea oral",
"practicalTasks": "Tareas prácticas", "practicalTasks": "Tareas prácticas",

View File

@@ -247,24 +247,27 @@
<p v-else class="vocab-prep-pass__ready">{{ $t('socialnetwork.vocab.courses.vocabPrepReady') }}</p> <p v-else class="vocab-prep-pass__ready">{{ $t('socialnetwork.vocab.courses.vocabPrepReady') }}</p>
</div> </div>
<!-- Wichtige Begriffe erst nach den zwei Durchgängen gesammelt anzeigen --> <!-- Gesamtübersicht: gleicher Lernsatz wie Vorbereitung und Trainer -->
<div <details
v-if="lesson && importantVocab && importantVocab.length > 0 && !vocabTrainerActive && lessonPrepStage >= 2" v-if="lesson && lessonVocab.length > 0 && !vocabTrainerActive"
class="vocab-list" class="vocab-list vocab-list--overview"
:open="lessonPrepStage >= 2"
> >
<h4>{{ $t('socialnetwork.vocab.courses.importantVocab') }}</h4> <summary class="vocab-list__summary">
{{ $t('socialnetwork.vocab.courses.vocabOverviewToggle') }}
</summary>
<p class="vocab-info-text">{{ $t('socialnetwork.vocab.courses.vocabInfoText') }}</p> <p class="vocab-info-text">{{ $t('socialnetwork.vocab.courses.vocabInfoText') }}</p>
<div class="vocab-items"> <div class="vocab-items">
<div v-for="(vocab, index) in importantVocab" :key="index" class="vocab-item"> <div v-for="(vocab, index) in lessonVocab" :key="index" class="vocab-item">
<strong>{{ vocab.learning }}</strong> <strong>{{ vocab.learning || '—' }}</strong>
<span class="separator"></span> <span class="separator"></span>
<span>{{ vocab.reference }}</span> <span>{{ vocab.reference }}</span>
</div> </div>
</div> </div>
</div> </details>
<!-- Vokabeltrainer --> <!-- Vokabeltrainer -->
<div v-if="importantVocab && importantVocab.length > 0" class="vocab-trainer-section"> <div v-if="trainableLessonVocab.length > 0" class="vocab-trainer-section">
<h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4> <h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4>
<div v-if="hasPreviousVocab" class="review-priority-note"> <div v-if="hasPreviousVocab" class="review-priority-note">
<strong>Wiederholung läuft schrittweise mit</strong> <strong>Wiederholung läuft schrittweise mit</strong>
@@ -367,7 +370,7 @@
</div> </div>
<!-- Hinweis wenn keine Vokabeln vorhanden --> <!-- Hinweis wenn keine Vokabeln vorhanden -->
<div v-else-if="lesson && (!importantVocab || importantVocab.length === 0)" class="no-vocab-info"> <div v-else-if="lesson && lessonVocab.length === 0" class="no-vocab-info">
<p>{{ $t('socialnetwork.vocab.courses.noVocabInfo') }}</p> <p>{{ $t('socialnetwork.vocab.courses.noVocabInfo') }}</p>
</div> </div>
@@ -830,7 +833,7 @@ export default {
return 1; return 1;
}, },
trainerNewFocusTarget() { trainerNewFocusTarget() {
const vocabCount = this.importantVocab?.length || 0; const vocabCount = this.trainableLessonVocab?.length || 0;
const exerciseCount = this.effectiveExercises?.length || 0; const exerciseCount = this.effectiveExercises?.length || 0;
const durationBonus = Math.max(0, Math.round((this.lesson?.targetMinutes || 0) / 5) - 1); const durationBonus = Math.max(0, Math.round((this.lesson?.targetMinutes || 0) / 5) - 1);
const baseTarget = Math.ceil((Math.max(vocabCount, 4) * 1.35) + (exerciseCount * 0.35) + durationBonus); const baseTarget = Math.ceil((Math.max(vocabCount, 4) * 1.35) + (exerciseCount * 0.35) + durationBonus);
@@ -858,9 +861,16 @@ export default {
canAccessExercises() { canAccessExercises() {
if (!this.hasExercises) return false; if (!this.hasExercises) return false;
const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review'; const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review';
return isReview || this.exercisePreparationCompleted; if (isReview) return true;
if (this.trainableLessonVocab.length === 0 && this.prepItems.length > 0) {
return this.lessonPrepStage >= 2;
}
return this.exercisePreparationCompleted;
}, },
exerciseUnlockHint() { exerciseUnlockHint() {
if (this.trainableLessonVocab.length === 0 && this.prepItems.length > 0) {
return this.$t('socialnetwork.vocab.courses.exerciseUnlockHintAfterPrep');
}
if (this.hasPreviousVocab) { if (this.hasPreviousVocab) {
return `Lerne zuerst die neuen Inhalte der Lektion und arbeite dich durch ungefähr ${this.trainerExerciseUnlockAttempts} Trainerfragen. Ältere Vokabeln werden dabei nach und nach zugemischt.`; return `Lerne zuerst die neuen Inhalte der Lektion und arbeite dich durch ungefähr ${this.trainerExerciseUnlockAttempts} Trainerfragen. Ältere Vokabeln werden dabei nach und nach zugemischt.`;
} }
@@ -931,6 +941,39 @@ export default {
return []; return [];
} }
}, },
lessonVocab() {
const vocabByReference = new Map();
const addEntry = (entry) => {
const reference = String(entry?.reference || '').trim();
const learning = String(entry?.learning || '').trim();
if (!reference) return;
const key = reference.toLowerCase();
if (!vocabByReference.has(key)) {
vocabByReference.set(key, { learning, reference });
return;
}
const existing = vocabByReference.get(key);
if (!existing.learning && learning) {
existing.learning = learning;
}
};
this.normalizedCorePatterns.forEach((item) => {
addEntry({
learning: item.gloss || '',
reference: item.target || ''
});
});
this.importantVocab.forEach((item) => {
addEntry(item);
});
return Array.from(vocabByReference.values());
},
trainableLessonVocab() {
return this.lessonVocab.filter((entry) => entry.learning && entry.reference && entry.learning !== entry.reference);
},
lessonDidactics() { lessonDidactics() {
return this.lesson?.didactics || { return this.lesson?.didactics || {
learningGoals: [], learningGoals: [],
@@ -947,21 +990,7 @@ export default {
.filter(Boolean); .filter(Boolean);
}, },
prepItems() { prepItems() {
if (this.normalizedCorePatterns.length > 0) { return this.lessonVocab
const glossByReference = new Map(
(this.importantVocab || [])
.map((item) => [String(item?.reference || '').trim().toLowerCase(), String(item?.learning || '').trim()])
.filter(([reference, learning]) => reference && learning)
);
return this.normalizedCorePatterns.map((item) => {
if (item.gloss) {
return item;
}
const gloss = glossByReference.get(String(item.target || '').trim().toLowerCase()) || '';
return gloss ? { ...item, gloss } : item;
});
}
return (this.importantVocab || [])
.map((item) => ({ .map((item) => ({
target: String(item?.reference || '').trim(), target: String(item?.reference || '').trim(),
gloss: String(item?.learning || '').trim() gloss: String(item?.learning || '').trim()
@@ -981,7 +1010,7 @@ export default {
return this.normalizedCorePatterns.some((p) => p.gloss); return this.normalizedCorePatterns.some((p) => p.gloss);
}, },
canStartVocabTrainerPrep() { canStartVocabTrainerPrep() {
if (!this.importantVocab || this.importantVocab.length === 0) { if (!this.trainableLessonVocab || this.trainableLessonVocab.length === 0) {
return false; return false;
} }
if (this.prepItems.length > 0 && this.lessonPrepStage < 2) { if (this.prepItems.length > 0 && this.lessonPrepStage < 2) {
@@ -1794,17 +1823,17 @@ export default {
// Vokabeltrainer-Methoden // Vokabeltrainer-Methoden
startVocabTrainer() { startVocabTrainer() {
debugLog('[VocabLessonView] startVocabTrainer aufgerufen'); debugLog('[VocabLessonView] startVocabTrainer aufgerufen');
if (!this.importantVocab || this.importantVocab.length === 0) { if (!this.trainableLessonVocab || this.trainableLessonVocab.length === 0) {
debugLog('[VocabLessonView] Keine Vokabeln vorhanden'); debugLog('[VocabLessonView] Keine Vokabeln vorhanden');
return; return;
} }
if (!this.canStartVocabTrainerPrep) { if (!this.canStartVocabTrainerPrep) {
return; return;
} }
debugLog('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length); debugLog('[VocabLessonView] Vokabeln gefunden:', this.trainableLessonVocab.length);
debugLog('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0); debugLog('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0);
this.vocabTrainerActive = true; this.vocabTrainerActive = true;
this.vocabTrainerPool = [...this.importantVocab]; this.vocabTrainerPool = [...this.trainableLessonVocab];
this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false; this.vocabTrainerAutoSwitchedToTyping = false;
this.vocabTrainerCorrect = 0; this.vocabTrainerCorrect = 0;
@@ -1817,7 +1846,7 @@ export default {
this.vocabTrainerMixedPool = this._buildMixedPool(); this.vocabTrainerMixedPool = this._buildMixedPool();
this.vocabTrainerPhase = 'current'; this.vocabTrainerPhase = 'current';
this.vocabTrainerMixedAttempts = 0; this.vocabTrainerMixedAttempts = 0;
this.vocabTrainerPool = [...this.importantVocab]; this.vocabTrainerPool = [...this.trainableLessonVocab];
debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln'); debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
debugLog('[VocabLessonView] Rufe nextVocabQuestion auf'); debugLog('[VocabLessonView] Rufe nextVocabQuestion auf');
this.$nextTick(() => { this.$nextTick(() => {
@@ -1841,7 +1870,7 @@ export default {
/** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */ /** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */
_buildMixedPool() { _buildMixedPool() {
if (!this.previousVocab || this.previousVocab.length === 0) return []; if (!this.previousVocab || this.previousVocab.length === 0) return [];
const currentKeys = new Set(this.importantVocab.map(v => this.getVocabKey(v))); const currentKeys = new Set(this.trainableLessonVocab.map(v => this.getVocabKey(v)));
const filtered = this.previousVocab.filter(v => !currentKeys.has(this.getVocabKey(v))); const filtered = this.previousVocab.filter(v => !currentKeys.has(this.getVocabKey(v)));
// Zufällig mischen und auf 40 begrenzen // Zufällig mischen und auf 40 begrenzen
const shuffled = [...filtered].sort(() => Math.random() - 0.5); const shuffled = [...filtered].sort(() => Math.random() - 0.5);
@@ -1868,7 +1897,7 @@ export default {
debugLog('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing'); debugLog('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing');
this.vocabTrainerMode = 'typing'; this.vocabTrainerMode = 'typing';
this.vocabTrainerAutoSwitchedToTyping = true; this.vocabTrainerAutoSwitchedToTyping = true;
this.vocabTrainerPool = [...this.importantVocab, ...this.vocabTrainerMixedPool]; this.vocabTrainerPool = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
this.vocabTrainerCorrect = 0; this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0; this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0; this.vocabTrainerTotalAttempts = 0;
@@ -1880,8 +1909,8 @@ export default {
this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false; this.vocabTrainerAutoSwitchedToTyping = false;
this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed' this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed'
? [...this.importantVocab, ...this.vocabTrainerMixedPool] ? [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool]
: [...this.importantVocab]; : [...this.trainableLessonVocab];
// Reset Stats für Multiple Choice Modus // Reset Stats für Multiple Choice Modus
this.vocabTrainerCorrect = 0; this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0; this.vocabTrainerWrong = 0;
@@ -1988,7 +2017,7 @@ export default {
this.checkVocabModeSwitch(); this.checkVocabModeSwitch();
let questionSource = 'current'; let questionSource = 'current';
let sourcePool = this.importantVocab; let sourcePool = this.trainableLessonVocab;
if (this.vocabTrainerMode === 'typing') { if (this.vocabTrainerMode === 'typing') {
sourcePool = this.vocabTrainerPool; sourcePool = this.vocabTrainerPool;
@@ -2001,14 +2030,14 @@ export default {
} }
if (!sourcePool || sourcePool.length === 0) { if (!sourcePool || sourcePool.length === 0) {
sourcePool = this.importantVocab; sourcePool = this.trainableLessonVocab;
questionSource = 'current'; questionSource = 'current';
} }
const randomIndex = Math.floor(Math.random() * sourcePool.length); const randomIndex = Math.floor(Math.random() * sourcePool.length);
const vocab = sourcePool[randomIndex]; const vocab = sourcePool[randomIndex];
this.vocabTrainerDirection = Math.random() < 0.5 ? 'L2R' : 'R2L'; this.vocabTrainerDirection = Math.random() < 0.5 ? 'L2R' : 'R2L';
const allTrainerVocabs = [...this.importantVocab, ...this.vocabTrainerMixedPool]; const allTrainerVocabs = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
const prompt = this.vocabTrainerDirection === 'L2R' ? vocab.learning : vocab.reference; const prompt = this.vocabTrainerDirection === 'L2R' ? vocab.learning : vocab.reference;
const acceptableAnswers = this.getEquivalentVocabAnswers( const acceptableAnswers = this.getEquivalentVocabAnswers(
prompt, prompt,
@@ -2795,6 +2824,17 @@ export default {
margin: 20px 0; margin: 20px 0;
} }
.vocab-list__summary {
cursor: pointer;
font-weight: 600;
color: #5f4313;
margin-bottom: 12px;
}
.vocab-list[open] .vocab-list__summary {
margin-bottom: 14px;
}
.vocab-list h4 { .vocab-list h4 {
margin-bottom: 15px; margin-bottom: 15px;
color: #333; color: #333;