diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue
index a596073..f7eee55 100644
--- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue
+++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue
@@ -94,6 +94,12 @@
+
+
@@ -137,6 +143,10 @@
{{ $t('socialnetwork.vocab.practice.fail') }}
{{ wrongCount }} ({{ failPercent }}%)
+
+ {{ $t('socialnetwork.vocab.practice.hardCount') }}
+ {{ hardCount }}
+
@@ -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) {
diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json
index e52e9ef..e44f98b 100644
--- a/frontend/src/i18n/locales/de/socialnetwork.json
+++ b/frontend/src/i18n/locales/de/socialnetwork.json
@@ -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",
diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json
index d2f4f2a..e444dff 100644
--- a/frontend/src/i18n/locales/en/socialnetwork.json
+++ b/frontend/src/i18n/locales/en/socialnetwork.json
@@ -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",