const searchEl = document.getElementById("explorer-search"); const srsFilterEl = document.getElementById("explorer-srs-filter"); const sortEl = document.getElementById("explorer-sort"); const countEl = document.getElementById("explorer-count"); const metaEl = document.getElementById("explorer-meta"); const listEl = document.getElementById("explorer-list"); const SRS_KEY = "frenchJourney.srs"; const FORCE_KEY = "frenchJourney.forceCard"; const DEFAULT_LIST_ID = "er-verbs"; const MIN = 60000; const DAY = 86400000; let cards = []; let query = ""; let srsFilter = "all"; let sortMode = "srs"; let store = { cards: {}, meta: {} }; const PRONOUNS = ["je", "tu", "il/elle", "nous", "vous", "ils/elles"]; function escapeHtml(s) { return String(s).replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }[ch])); } function withPronoun(pron, form) { if (pron === "je" && /^[aeiouyhàâäéèêëîïôöûü]/i.test(form)) { return `j'${escapeHtml(form)}`; } return `${pron} ${escapeHtml(form)}`; } function conjTable(card) { if (!card.present) return ""; const cells = card.present .map((form, i) => `${withPronoun(PRONOUNS[i], form)}`) .join(""); return `
Présent
${cells}
`; } function loadSrs() { try { const saved = JSON.parse(localStorage.getItem(SRS_KEY)); if (saved && saved.cards) store = saved; } catch {} } function cardKey(card) { return `${DEFAULT_LIST_ID}:${card.infinitif}`; } function schedOf(card) { return store.cards[cardKey(card)]; } function fmtUntil(ms) { if (ms <= 0) return "due now"; if (ms < DAY) { const mins = Math.round(ms / MIN); if (mins < 1) return "<1m"; if (mins < 60) return `${mins}m`; return `${Math.round(mins / 60)}h`; } const days = ms / DAY; if (days < 30) return `${Math.round(days)}d`; if (days < 365) return `${(days / 30).toFixed(1).replace(/\.0$/, "")}mo`; return `${(days / 365).toFixed(1).replace(/\.0$/, "")}y`; } function srsInfo(card) { const s = schedOf(card); if (!s) return { state: "new", label: "new", timeLeft: -1 }; const isLearning = s.state === "learning" || s.state === "relearning"; const timeLeft = Math.max(0, s.due - Date.now()); return { state: s.state || "review", label: timeLeft === 0 ? (isLearning ? "learning now" : "due now") : fmtUntil(timeLeft), timeLeft, }; } function matches(card, q) { if (!q) return true; return [ card.infinitif, card.anglais, card.exemple_fr, card.exemple_en, ].join(" ").toLowerCase().includes(q); } function matchesSrs(card) { const s = schedOf(card); if (srsFilter === "all") return true; if (srsFilter === "new") return !s; if (srsFilter === "scheduled") return !!s; if (srsFilter === "due") return !!s && s.due <= Date.now(); return true; } function sortCards(a, b) { if (sortMode === "az") return a.infinitif.localeCompare(b.infinitif, "fr"); const ai = srsInfo(a); const bi = srsInfo(b); if (ai.timeLeft !== bi.timeLeft) return ai.timeLeft - bi.timeLeft; return a.infinitif.localeCompare(b.infinitif, "fr"); } function studyCard(key) { localStorage.setItem(FORCE_KEY, key); window.location.href = "flashcards.html"; } function render() { const q = query.trim().toLowerCase(); const filtered = cards .filter((card) => matches(card, q) && matchesSrs(card)) .sort(sortCards); countEl.textContent = String(cards.length); metaEl.textContent = `${filtered.length} of ${cards.length} cards`; if (!cards.length) { listEl.innerHTML = `
No cards loaded.
`; return; } if (!filtered.length) { listEl.innerHTML = `
No cards match this filter.
`; return; } listEl.innerHTML = filtered.map((card) => `
Front ${escapeHtml(card.infinitif)} ${escapeHtml(srsInfo(card).label)}
Back ${escapeHtml(card.anglais)} ${conjTable(card)}
${escapeHtml(card.exemple_fr)} ${escapeHtml(card.exemple_en)}
`).join(""); listEl.querySelectorAll("[data-card-key]").forEach((button) => { button.onclick = () => studyCard(button.dataset.cardKey); }); } async function init() { try { loadSrs(); const SQL = await initSqlJs({ locateFile: (f) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/${f}`, }); const buf = await fetch("verbs.db").then((r) => { if (!r.ok) throw new Error("HTTP " + r.status); return r.arrayBuffer(); }); const db = new SQL.Database(new Uint8Array(buf)); const res = db.exec("SELECT infinitif, anglais, exemple_fr, exemple_en FROM verbs ORDER BY infinitif;"); const rows = res[0]?.values || []; cards = rows.map(([infinitif, anglais, exemple_fr, exemple_en]) => ({ infinitif, anglais, exemple_fr, exemple_en, })); const conj = {}; const cres = db.exec("SELECT infinitif, p1, p2, p3, p4, p5, p6 FROM conjugations WHERE tense='présent';"); if (cres[0]) { for (const [infinitif, ...forms] of cres[0].values) conj[infinitif] = forms; } cards.forEach((card) => { card.present = conj[card.infinitif] || null; }); searchEl.oninput = () => { query = searchEl.value; render(); }; srsFilterEl.onchange = () => { srsFilter = srsFilterEl.value; render(); }; sortEl.onchange = () => { sortMode = sortEl.value; render(); }; render(); } catch (err) { metaEl.textContent = "Could not load database."; listEl.innerHTML = `
Couldn't load verbs.db.

Open this app through a local server:

python3 -m http.server 8000

${escapeHtml(err.message)}

`; } } init();