200 lines
3.7 KiB
JavaScript
200 lines
3.7 KiB
JavaScript
import { getDB } from './db.js';
|
|
|
|
/**
|
|
* HELPERS
|
|
*/
|
|
function store(storeName, mode = 'readonly') {
|
|
const db = getDB();
|
|
const tx = db.transaction(storeName, mode);
|
|
return tx.objectStore(storeName);
|
|
}
|
|
|
|
/**
|
|
* IMPORT WORD LIST FROM SERVER → INDEXEDDB
|
|
*/
|
|
export async function importList(lang, list) {
|
|
|
|
const res = await fetch(`/api/list.php?lang=${lang}&list=${list}`);
|
|
const data = await res.json();
|
|
|
|
if (!data.words || !Array.isArray(data.words)) {
|
|
console.error("Import failed:", data);
|
|
return;
|
|
}
|
|
|
|
const s = store('words', 'readwrite');
|
|
|
|
data.words.forEach(word => {
|
|
|
|
s.put({
|
|
key: `${lang}:${list}:${word.id}`,
|
|
id: word.id,
|
|
lang,
|
|
list,
|
|
question: word.question,
|
|
answer: word.answer,
|
|
correct: word.correct || 0,
|
|
wrong: word.wrong || 0,
|
|
nextReview: word.nextReview || null,
|
|
interval: word.interval || 1
|
|
});
|
|
});
|
|
|
|
console.log(`Imported ${data.words.length} words`);
|
|
}
|
|
|
|
/**
|
|
* GET WORDS
|
|
*/
|
|
export async function getWords(lang, list) {
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
const s = store('words');
|
|
|
|
const req = s.getAll();
|
|
|
|
req.onsuccess = () => {
|
|
|
|
const filtered = req.result.filter(
|
|
w => w.lang === lang && w.list === list
|
|
);
|
|
|
|
resolve(filtered);
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* GET NEXT WORD (SRS SIMPLE)
|
|
*/
|
|
export async function getNextWord(lang, list) {
|
|
|
|
const words = await getWords(lang, list);
|
|
|
|
if (!words.length) {
|
|
console.warn("No words in IndexedDB");
|
|
return null;
|
|
}
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
let due = words.filter(w =>
|
|
!w.nextReview || w.nextReview <= today
|
|
);
|
|
|
|
if (!due.length) {
|
|
due = words;
|
|
}
|
|
|
|
return due[Math.floor(Math.random() * due.length)];
|
|
}
|
|
|
|
/**
|
|
* ANSWER WORD
|
|
*/
|
|
export async function answerWord(word, input) {
|
|
|
|
const correct =
|
|
word.answer.trim().toLowerCase() ===
|
|
input.trim().toLowerCase();
|
|
|
|
const s = store('words', 'readwrite');
|
|
|
|
const updated = { ...word };
|
|
|
|
if (correct) {
|
|
|
|
updated.correct++;
|
|
|
|
const days = updated.interval * 2;
|
|
updated.interval = days;
|
|
|
|
const d = new Date();
|
|
d.setDate(d.getDate() + days);
|
|
|
|
updated.nextReview =
|
|
d.toISOString().split('T')[0];
|
|
|
|
} else {
|
|
|
|
updated.wrong++;
|
|
|
|
updated.interval = 1;
|
|
|
|
const d = new Date();
|
|
d.setDate(d.getDate() + 1);
|
|
|
|
updated.nextReview =
|
|
d.toISOString().split('T')[0];
|
|
}
|
|
|
|
s.put(updated);
|
|
|
|
updateSessionStats(correct);
|
|
|
|
queueSync({
|
|
wordId: word.id,
|
|
lang: word.lang,
|
|
list: word.list,
|
|
correct,
|
|
ts: Date.now()
|
|
});
|
|
|
|
return { correct };
|
|
}
|
|
|
|
/**
|
|
* SESSION STATS
|
|
*/
|
|
export function updateSessionStats(correct) {
|
|
|
|
const s = store('session', 'readwrite');
|
|
|
|
const req = s.get('stats');
|
|
|
|
req.onsuccess = () => {
|
|
|
|
let stats = req.result;
|
|
|
|
if (!stats) {
|
|
stats = {
|
|
key: 'stats',
|
|
correct: 0,
|
|
wrong: 0
|
|
};
|
|
}
|
|
|
|
if (correct) stats.correct++;
|
|
else stats.wrong++;
|
|
|
|
s.put(stats);
|
|
};
|
|
}
|
|
|
|
export async function getSessionStats() {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const s = store('session');
|
|
|
|
const req = s.get('stats');
|
|
|
|
req.onsuccess = () => {
|
|
|
|
resolve(req.result || {
|
|
correct: 0,
|
|
wrong: 0
|
|
});
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* SYNC QUEUE
|
|
*/
|
|
function queueSync(item) {
|
|
|
|
const s = store('sync_queue', 'readwrite');
|
|
s.add(item);
|
|
} |