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