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">
|
<button v-else-if="showSkipButton" @click="skip">
|
||||||
{{ $t('socialnetwork.vocab.practice.skip') }}
|
{{ $t('socialnetwork.vocab.practice.skip') }}
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<div v-if="lastWrongReview" class="solution-card solution-card--persistent">
|
<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="k">{{ $t('socialnetwork.vocab.practice.fail') }}</span>
|
||||||
<span class="v">{{ wrongCount }} ({{ failPercent }}%)</span>
|
<span class="v">{{ wrongCount }} ({{ failPercent }}%)</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="statRow">
|
||||||
|
<span class="k">{{ $t('socialnetwork.vocab.practice.hardCount') }}</span>
|
||||||
|
<span class="v">{{ hardCount }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,6 +198,7 @@ export default {
|
|||||||
lastCorrect: false,
|
lastCorrect: false,
|
||||||
locked: false,
|
locked: false,
|
||||||
autoAdvanceTimer: null,
|
autoAdvanceTimer: null,
|
||||||
|
hardVocabMap: {}, // { [normalizedPairKey]: { learning, reference, markedAt } }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -277,8 +288,70 @@ export default {
|
|||||||
const total = this.srsTotalDue || 0;
|
const total = this.srsTotalDue || 0;
|
||||||
return Math.max(0, total - this.srsDoneCount);
|
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: {
|
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() {
|
getLocalDateKey() {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
const y = d.getFullYear();
|
const y = d.getFullYear();
|
||||||
@@ -381,6 +454,7 @@ export default {
|
|||||||
this.lastWrongReview = null;
|
this.lastWrongReview = null;
|
||||||
this.pendingRetry = null;
|
this.pendingRetry = null;
|
||||||
this.pool = [];
|
this.pool = [];
|
||||||
|
this.hardVocabMap = {};
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
this.resetQuestion();
|
this.resetQuestion();
|
||||||
this.$refs.dialog.open();
|
this.$refs.dialog.open();
|
||||||
@@ -388,6 +462,7 @@ export default {
|
|||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
});
|
});
|
||||||
this.reloadPool();
|
this.reloadPool();
|
||||||
|
this.loadHardVocabMap();
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
if (this.autoAdvanceTimer) {
|
if (this.autoAdvanceTimer) {
|
||||||
|
|||||||
@@ -461,6 +461,9 @@
|
|||||||
"remaining": "Noch offen",
|
"remaining": "Noch offen",
|
||||||
"success": "Erfolg",
|
"success": "Erfolg",
|
||||||
"fail": "Misserfolg",
|
"fail": "Misserfolg",
|
||||||
|
"hardCount": "Schwer markiert",
|
||||||
|
"markHard": "Als schwer markieren",
|
||||||
|
"unmarkHard": "Aus schwer entfernen",
|
||||||
"srsRateTitle": "Wie sicher war das?",
|
"srsRateTitle": "Wie sicher war das?",
|
||||||
"srsAgain": "Nochmal",
|
"srsAgain": "Nochmal",
|
||||||
"srsAgainHint": "sehr bald wiederholen",
|
"srsAgainHint": "sehr bald wiederholen",
|
||||||
|
|||||||
@@ -461,6 +461,9 @@
|
|||||||
"remaining": "Remaining",
|
"remaining": "Remaining",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"fail": "Fail",
|
"fail": "Fail",
|
||||||
|
"hardCount": "Marked difficult",
|
||||||
|
"markHard": "Mark as difficult",
|
||||||
|
"unmarkHard": "Remove difficult mark",
|
||||||
"srsRateTitle": "How solid did it feel?",
|
"srsRateTitle": "How solid did it feel?",
|
||||||
"srsAgain": "Again",
|
"srsAgain": "Again",
|
||||||
"srsAgainHint": "repeat very soon",
|
"srsAgainHint": "repeat very soon",
|
||||||
|
|||||||
Reference in New Issue
Block a user