feat(vocab): synchronize SRS session progress and update related UI elements
This commit is contained in:
@@ -472,6 +472,9 @@ export default {
|
||||
session.correctCount = this.correctCount;
|
||||
session.wrongCount = this.wrongCount;
|
||||
localStorage.setItem(key, JSON.stringify(session));
|
||||
window.dispatchEvent(new CustomEvent('yourpart:vocab:srsSession:changed', {
|
||||
detail: { courseId: this.openParams.courseId }
|
||||
}));
|
||||
} catch (_) {
|
||||
// ignore storage issues (private mode, quota, etc.)
|
||||
}
|
||||
|
||||
@@ -870,9 +870,9 @@
|
||||
"reviewTimeNow": "jetzt",
|
||||
"reviewTimeTomorrow": "morgen",
|
||||
"reviewTimeInDays": "in {count} Tagen",
|
||||
"srsDueStat": "Tageswiederholung: {scheduled} von {total}",
|
||||
"srsDueStat": "Tageswiederholung: {scheduled} Begriffe",
|
||||
"srsEyebrow": "Langzeitgedächtnis",
|
||||
"srsTitle": "Heute: {scheduled} von {total} fälligen Begriffen",
|
||||
"srsTitle": "Heute: {scheduled} fällige Begriffe",
|
||||
"srsIntro": "Diese Wiederholung kommt aus dem SRS-Plan einzelner Begriffe. Pro Tag werden höchstens 50 Begriffe eingeplant; weitere fällige Begriffe bleiben für die nächste Tageswiederholung erhalten.",
|
||||
"srsStart": "Tageswiederholung starten",
|
||||
"courseTodayPlanIntroSrs": "Didaktische Reihenfolge: Zuerst die fällige SRS-Tageswiederholung einzelner Begriffe. Danach kommen Kurz-Wiederholungen, Blockfortschritt und ggf. intensive Wiederholung. So wird altes Material stabilisiert, bevor neues Material dazukommt."
|
||||
|
||||
@@ -870,9 +870,9 @@
|
||||
"reviewTimeNow": "now",
|
||||
"reviewTimeTomorrow": "tomorrow",
|
||||
"reviewTimeInDays": "in {count} days",
|
||||
"srsDueStat": "Daily review: {scheduled} of {total}",
|
||||
"srsDueStat": "Daily review: {scheduled} terms",
|
||||
"srsEyebrow": "Long-term memory",
|
||||
"srsTitle": "Today: {scheduled} of {total} due terms",
|
||||
"srsTitle": "Today: {scheduled} due terms",
|
||||
"srsIntro": "This review comes from the SRS schedule of individual terms. At most 50 terms are planned per day; any additional due terms remain for the next daily review.",
|
||||
"srsStart": "Start daily review",
|
||||
"courseTodayPlanIntroSrs": "Pedagogical order: start with the due SRS daily review for individual terms. Then quick reviews, block progress, and intensive review if needed. This stabilizes old material before new material is added."
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="srsDueCount > 0" class="course-srs-plan">
|
||||
<div v-if="srsDailyCount > 0" class="course-srs-plan">
|
||||
<div>
|
||||
<span class="course-srs-plan__eyebrow">{{ $t('socialnetwork.vocab.courses.srsEyebrow') }}</span>
|
||||
<h4>{{ $t('socialnetwork.vocab.courses.srsTitle', { scheduled: srsDailyCount, total: srsDueCount }) }}</h4>
|
||||
@@ -98,7 +98,7 @@
|
||||
<div v-if="todayRecommendedSteps.length > 0" class="course-today-plan">
|
||||
<h4 class="course-today-plan__title">{{ $t('socialnetwork.vocab.courses.courseTodayPlanTitle') }}</h4>
|
||||
<p class="course-today-plan__intro">
|
||||
{{ srsDueCount > 0
|
||||
{{ srsDailyCount > 0
|
||||
? $t('socialnetwork.vocab.courses.courseTodayPlanIntroSrs')
|
||||
: dueReviewLessons.length > 0
|
||||
? $t('socialnetwork.vocab.courses.courseTodayPlanIntro')
|
||||
@@ -354,6 +354,8 @@ import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feed
|
||||
import VocabPracticeDialog from '@/dialogues/socialnetwork/VocabPracticeDialog.vue';
|
||||
import { localizeVocabCourseTitle } from '@/utils/vocabCourseTitle.js';
|
||||
|
||||
const SRS_SESSION_STORAGE_VERSION = 2;
|
||||
|
||||
export default {
|
||||
name: 'VocabCourseView',
|
||||
components: { VocabPracticeDialog },
|
||||
@@ -372,6 +374,7 @@ export default {
|
||||
srsDueItems: [],
|
||||
srsDueTotal: 0,
|
||||
srsDailyLimit: 50,
|
||||
srsTodayRemaining: null,
|
||||
srsLoading: false,
|
||||
showAddLessonDialog: false,
|
||||
assistantSettings: null,
|
||||
@@ -422,6 +425,9 @@ export default {
|
||||
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
|
||||
},
|
||||
srsDailyCount() {
|
||||
if (Number.isFinite(Number(this.srsTodayRemaining))) {
|
||||
return Math.max(0, Number(this.srsTodayRemaining));
|
||||
}
|
||||
return Math.min(this.srsDueCount, this.srsDailyLimit);
|
||||
},
|
||||
hardVocabCount() {
|
||||
@@ -563,6 +569,29 @@ export default {
|
||||
displayCourseTitle(course) {
|
||||
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
|
||||
},
|
||||
getLocalDateKey() {
|
||||
const date = new Date();
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
},
|
||||
syncSrsSessionProgress() {
|
||||
const key = `yourpart:vocab:srsSession:v${SRS_SESSION_STORAGE_VERSION}:${this.courseId}:${this.getLocalDateKey()}`;
|
||||
try {
|
||||
const session = JSON.parse(localStorage.getItem(key) || 'null');
|
||||
const isCurrentSession = session
|
||||
&& session.version === SRS_SESSION_STORAGE_VERSION
|
||||
&& String(session.courseId) === String(this.courseId)
|
||||
&& session.dateKey === this.getLocalDateKey();
|
||||
if (!isCurrentSession) {
|
||||
this.srsTodayRemaining = null;
|
||||
return;
|
||||
}
|
||||
const total = Math.max(0, Number(session.initialTotalDue) || 0);
|
||||
const done = new Set(Array.isArray(session.doneIds) ? session.doneIds : []).size;
|
||||
this.srsTodayRemaining = Math.max(0, total - done);
|
||||
} catch (_) {
|
||||
this.srsTodayRemaining = null;
|
||||
}
|
||||
},
|
||||
async refreshHardVocabList() {
|
||||
if (!this.courseId) {
|
||||
this.hardVocabList = [];
|
||||
@@ -586,6 +615,12 @@ export default {
|
||||
},
|
||||
handleWindowFocus() {
|
||||
this.refreshHardVocabList();
|
||||
this.syncSrsSessionProgress();
|
||||
},
|
||||
handleSrsSessionChanged(event) {
|
||||
if (String(event?.detail?.courseId || '') === String(this.courseId)) {
|
||||
this.syncSrsSessionProgress();
|
||||
}
|
||||
},
|
||||
handleHardVocabChanged(event) {
|
||||
const detailCourseId = event?.detail?.courseId;
|
||||
@@ -635,6 +670,7 @@ export default {
|
||||
this.srsDailyLimit = 50;
|
||||
} finally {
|
||||
this.srsLoading = false;
|
||||
this.syncSrsSessionProgress();
|
||||
}
|
||||
},
|
||||
async loadChapters() {
|
||||
@@ -1009,10 +1045,13 @@ export default {
|
||||
this.loadAssistantSettings()
|
||||
]);
|
||||
this.refreshHardVocabList();
|
||||
this.syncSrsSessionProgress();
|
||||
window.addEventListener('focus', this.handleWindowFocus);
|
||||
window.addEventListener('yourpart:vocab:srsSession:changed', this.handleSrsSessionChanged);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('focus', this.handleWindowFocus);
|
||||
window.removeEventListener('yourpart:vocab:srsSession:changed', this.handleSrsSessionChanged);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user