feat(VocabCourseView): add hard vocabulary practice button and refresh logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s

- Introduced a button to start hard vocabulary practice when applicable, enhancing user engagement with challenging vocabulary.
- Implemented methods to manage and refresh the hard vocabulary list from local storage, ensuring users have access to their difficult words.
- Added event listeners to refresh the hard vocabulary list on window focus, improving the overall user experience.
This commit is contained in:
Torsten Schulz (local)
2026-04-23 13:10:05 +02:00
parent cab5428d0b
commit 783dd175e8

View File

@@ -16,6 +16,14 @@
<span v-if="course.shareCode && isOwner" class="share-code">
{{ $t('socialnetwork.vocab.courses.shareCode') }}: <code>{{ course.shareCode }}</code>
</span>
<button
v-if="hardVocabCount > 0"
type="button"
class="course-hard-practice-link"
@click="openHardPractice"
>
{{ $t('socialnetwork.vocab.courses.startHardVocabTrainer', { count: hardVocabCount }) }}
</button>
<button type="button" class="course-dictionary-link" @click="goCourseDictionary">
{{ $t('socialnetwork.vocab.dictionary.open') }}
</button>
@@ -345,6 +353,7 @@ export default {
srsLoading: false,
showAddLessonDialog: false,
assistantSettings: null,
hardVocabList: [],
lessonFormTouched: false,
newLesson: {
lessonNumber: 1,
@@ -390,6 +399,9 @@ export default {
}
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
},
hardVocabCount() {
return Array.isArray(this.hardVocabList) ? this.hardVocabList.length : 0;
},
dueReviewLessons() {
return this.sortedLessons
.filter((lesson) => {
@@ -519,12 +531,45 @@ export default {
watch: {
courseId() {
this.loadCourse();
this.refreshHardVocabList();
}
},
methods: {
displayCourseTitle(course) {
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
},
hardStorageKey() {
return this.courseId ? `yourpart:vocab:hardList:${this.courseId}` : null;
},
refreshHardVocabList() {
const key = this.hardStorageKey();
if (!key) {
this.hardVocabList = [];
return;
}
try {
const raw = localStorage.getItem(key);
const parsed = raw ? JSON.parse(raw) : {};
const values = parsed && typeof parsed === 'object' ? Object.values(parsed) : [];
this.hardVocabList = values
.map((entry, idx) => {
const learning = String(entry?.learning || '').trim();
const reference = String(entry?.reference || '').trim();
if (!learning || !reference) return null;
return {
id: `hard-${idx}-${learning}-${reference}`,
learning,
reference
};
})
.filter(Boolean);
} catch (_) {
this.hardVocabList = [];
}
},
handleWindowFocus() {
this.refreshHardVocabList();
},
async loadCourse() {
this.loading = true;
try {
@@ -858,6 +903,14 @@ export default {
lessonId: lesson.id
});
},
openHardPractice() {
if (!this.hardVocabCount) return;
this.$refs.practiceDialog?.open?.({
courseId: this.courseId,
initialPool: this.hardVocabList,
onClose: () => this.refreshHardVocabList()
});
},
openSrsPractice() {
if (!this.srsDueItems.length) {
return;
@@ -922,6 +975,11 @@ export default {
this.loadCourse(),
this.loadAssistantSettings()
]);
this.refreshHardVocabList();
window.addEventListener('focus', this.handleWindowFocus);
},
beforeUnmount() {
window.removeEventListener('focus', this.handleWindowFocus);
},
};
</script>
@@ -978,6 +1036,10 @@ export default {
margin-left: auto;
}
.course-hard-practice-link {
flex-shrink: 0;
}
.course-assistant {
display: flex;
align-items: flex-start;