feat(VocabPracticeDialog, localization): add hard vocabulary marking feature
All checks were successful
Deploy to production / deploy (push) Successful in 1m53s
All checks were successful
Deploy to production / deploy (push) Successful in 1m53s
- Introduced functionality to mark vocabulary items as difficult, allowing users to track and manage challenging words during practice sessions. - Added buttons for marking and unmarking current vocabulary as hard, enhancing user engagement and learning effectiveness. - Updated the UI to display the count of hard-marked vocabulary items, providing users with better insights into their learning progress. - Enhanced localization files for German and English to support new vocabulary marking features, ensuring a consistent user experience across languages.
This commit is contained in:
@@ -94,6 +94,12 @@
|
||||
<button v-else-if="showSkipButton" @click="skip">
|
||||
{{ $t('socialnetwork.vocab.practice.skip') }}
|
||||
</button>
|
||||
<button v-if="current && !isCurrentMarkedHard" @click="markCurrentAsHard">
|
||||
{{ $t('socialnetwork.vocab.practice.markHard') }}
|
||||
</button>
|
||||
<button v-else-if="current && isCurrentMarkedHard" @click="unmarkCurrentAsHard">
|
||||
{{ $t('socialnetwork.vocab.practice.unmarkHard') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="lastWrongReview" class="solution-card solution-card--persistent">
|
||||
@@ -137,6 +143,10 @@
|
||||
<span class="k">{{ $t('socialnetwork.vocab.practice.fail') }}</span>
|
||||
<span class="v">{{ wrongCount }} ({{ failPercent }}%)</span>
|
||||
</div>
|
||||
<div class="statRow">
|
||||
<span class="k">{{ $t('socialnetwork.vocab.practice.hardCount') }}</span>
|
||||
<span class="v">{{ hardCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,6 +198,7 @@ export default {
|
||||
lastCorrect: false,
|
||||
locked: false,
|
||||
autoAdvanceTimer: null,
|
||||
hardVocabMap: {}, // { [normalizedPairKey]: { learning, reference, markedAt } }
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -277,8 +288,70 @@ export default {
|
||||
const total = this.srsTotalDue || 0;
|
||||
return Math.max(0, total - this.srsDoneCount);
|
||||
},
|
||||
hardCount() {
|
||||
return Object.keys(this.hardVocabMap || {}).length;
|
||||
},
|
||||
isCurrentMarkedHard() {
|
||||
if (!this.current) return false;
|
||||
return Boolean(this.hardVocabMap[this.getHardKey(this.current)]);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hardStorageKey() {
|
||||
const courseId = this.openParams?.courseId;
|
||||
if (!courseId) return null;
|
||||
return `yourpart:vocab:hardList:${courseId}`;
|
||||
},
|
||||
loadHardVocabMap() {
|
||||
const key = this.hardStorageKey();
|
||||
if (!key) {
|
||||
this.hardVocabMap = {};
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
const parsed = raw ? JSON.parse(raw) : {};
|
||||
this.hardVocabMap = parsed && typeof parsed === 'object' ? parsed : {};
|
||||
} catch (_) {
|
||||
this.hardVocabMap = {};
|
||||
}
|
||||
},
|
||||
saveHardVocabMap() {
|
||||
const key = this.hardStorageKey();
|
||||
if (!key) return;
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(this.hardVocabMap || {}));
|
||||
} catch (_) {
|
||||
// ignore quota/private-mode issues
|
||||
}
|
||||
},
|
||||
getHardKey(item) {
|
||||
const learning = this.normalize(item?.learning || '');
|
||||
const reference = this.normalize(item?.reference || '');
|
||||
return `${learning}|${reference}`;
|
||||
},
|
||||
markCurrentAsHard() {
|
||||
if (!this.current) return;
|
||||
const key = this.getHardKey(this.current);
|
||||
this.hardVocabMap = {
|
||||
...this.hardVocabMap,
|
||||
[key]: {
|
||||
learning: this.current.learning,
|
||||
reference: this.current.reference,
|
||||
markedAt: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
this.saveHardVocabMap();
|
||||
},
|
||||
unmarkCurrentAsHard() {
|
||||
if (!this.current) return;
|
||||
const key = this.getHardKey(this.current);
|
||||
if (!this.hardVocabMap[key]) return;
|
||||
const next = { ...this.hardVocabMap };
|
||||
delete next[key];
|
||||
this.hardVocabMap = next;
|
||||
this.saveHardVocabMap();
|
||||
},
|
||||
getLocalDateKey() {
|
||||
const d = new Date();
|
||||
const y = d.getFullYear();
|
||||
@@ -381,6 +454,7 @@ export default {
|
||||
this.lastWrongReview = null;
|
||||
this.pendingRetry = null;
|
||||
this.pool = [];
|
||||
this.hardVocabMap = {};
|
||||
this.locked = false;
|
||||
this.resetQuestion();
|
||||
this.$refs.dialog.open();
|
||||
@@ -388,6 +462,7 @@ export default {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
});
|
||||
this.reloadPool();
|
||||
this.loadHardVocabMap();
|
||||
},
|
||||
close() {
|
||||
if (this.autoAdvanceTimer) {
|
||||
|
||||
@@ -461,6 +461,9 @@
|
||||
"remaining": "Noch offen",
|
||||
"success": "Erfolg",
|
||||
"fail": "Misserfolg",
|
||||
"hardCount": "Schwer markiert",
|
||||
"markHard": "Als schwer markieren",
|
||||
"unmarkHard": "Aus schwer entfernen",
|
||||
"srsRateTitle": "Wie sicher war das?",
|
||||
"srsAgain": "Nochmal",
|
||||
"srsAgainHint": "sehr bald wiederholen",
|
||||
|
||||
@@ -461,6 +461,9 @@
|
||||
"remaining": "Remaining",
|
||||
"success": "Success",
|
||||
"fail": "Fail",
|
||||
"hardCount": "Marked difficult",
|
||||
"markHard": "Mark as difficult",
|
||||
"unmarkHard": "Remove difficult mark",
|
||||
"srsRateTitle": "How solid did it feel?",
|
||||
"srsAgain": "Again",
|
||||
"srsAgainHint": "repeat very soon",
|
||||
|
||||
Reference in New Issue
Block a user