From 60697aecb182b1993442d14eb317278f690e8488 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sun, 21 Jun 2026 13:29:57 +0300 Subject: [PATCH] Revemo always kana view --- .gitignore | 1 + src/main.ts | 10 --------- src/style.css | 9 +++++++- src/sync.ts | 2 ++ src/training.ts | 58 +++++++++++++++++++++++++++++++++++-------------- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index a547bf3..3c635ea 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +jwl.tar.gz \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index e31c61d..10e2dac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -95,16 +95,6 @@ function renderWordsList(words: Word[]) { let reading = word.key; let translation = word.value; - if (word.more) { - try { - const parsed = JSON.parse(word.more); - reading = parsed.reading || word.key; - translation = parsed.translation || word.value; - } catch { - // ignore - } - } - li.innerHTML = ` ${reading} ${escapeHtml(translation)} diff --git a/src/style.css b/src/style.css index fe7c179..7df27c9 100644 --- a/src/style.css +++ b/src/style.css @@ -249,7 +249,6 @@ body { margin-bottom: 10px; display: flex; align-items: center; - gap: 12px; flex-wrap: wrap; box-shadow: var(--shadow); transition: border-color 0.2s; @@ -475,6 +474,14 @@ body { margin: 0.75rem 0; } +.training-score { + font-size: 0.85rem; + color: var(--accent); + margin-top: 0.75rem; + font-weight: 500; + opacity: 0.8; +} + .training-actions { padding: 0.75rem 0; } diff --git a/src/sync.ts b/src/sync.ts index cdcb9fe..a328c43 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -45,6 +45,7 @@ export async function uploadDatabase( console.log('[Sync] База данных успешно загружена.'); } catch (err) { console.error('[Sync] Ошибка загрузки:', err); + console.log(err); alert(`Ошибка загрузки: ${err instanceof Error ? err.message : String(err)}`); throw err; } @@ -67,6 +68,7 @@ export async function downloadDatabase( try { // 1. GET-запрос const url = `${apiBaseUrl.replace(/\/+$/, '')}/download/${encodeURIComponent(id)}`; + console.log(url); const response = await fetch(url); if (!response.ok) { diff --git a/src/training.ts b/src/training.ts index 6b1e137..09c3ac7 100644 --- a/src/training.ts +++ b/src/training.ts @@ -1,4 +1,4 @@ -import { CardSet } from './algorithm.ts'; +import { CardSet, calculatedScore } from './algorithm.ts'; import type { CardSetSettings, CardStatistics, WordData, WordOpenMode } from './algorithm.ts'; import { updateCardStat } from './db-manager.ts'; @@ -11,12 +11,6 @@ export interface WordItem { group_id: number; } -/** - * Извлекает значения полей из слова. - * Сначала ищет в основных полях (key, value, tags), - * потом в more (JSON-словарь). - * Если поле не найдено — пропускается (возвращается пустая строка, фильтруется). - */ function getWordFieldValues(word: WordItem, fields: string): string[] { const directFields: Record = { key: word.key, @@ -47,6 +41,8 @@ export class TrainingPage { private totalCards: number; private forwardFields: string; private backwardFields: string; + private seenWords = new Set(); + private shownCount = 0; constructor( container: HTMLElement, @@ -81,7 +77,7 @@ export class TrainingPage { this.container.innerHTML = `
- слов: ${total} + уникальных: 0 / ${total}
@@ -98,22 +94,37 @@ export class TrainingPage { }); } + private updateCounter(): void { + const total = this.totalCards; + const span = this.container.querySelector('.training-header span'); + if (span) { + span.textContent = `уникальных: ${this.seenWords.size} / ${total}`; + } + } + private renderCard(): void { const result = this.cardSet.next(); const word = this.words.find((w) => w.id === result.word.id); if (!word) return; - // Обновляем дату последнего открытия в БД + this.shownCount++; + + // Учитываем уникальные слова + if (!this.seenWords.has(result.word.id)) { + this.seenWords.add(result.word.id); + } + this.updateCounter(); + const stat = this.cardSet.set[this.cardSet.currentWordIndex!]; if (stat) { const now = Math.floor(Date.now() / 1000); - stat.last_open = Date.now(); // обновляем в памяти из алгоритма + stat.last_open = Date.now(); updateCardStat(stat.id, stat.score, now).catch((err) => console.error('[Training] Ошибка сохранения last_opened:', err), ); } - this.renderSide(word, this.forwardFields, this.backwardFields, false); + this.renderSide(word, this.forwardFields, this.backwardFields, false, stat); this.renderActions(false); } @@ -122,19 +133,31 @@ export class TrainingPage { forwardFields: string, backwardFields: string, showBack: boolean, + stat?: CardStatistics, ): void { const contentEl = document.getElementById('training-content'); if (!contentEl) return; + // Текущий балл слова (с учётом затухания) + let scoreStr = ''; + if (stat) { + const cs = calculatedScore(stat); + scoreStr = `⚡ ${cs.toFixed(1)}`; + } + if (!showBack) { const values = getWordFieldValues(word, forwardFields); if (values.length === 0) { - contentEl.innerHTML = '
Нет данных для отображения
'; + contentEl.innerHTML = ` +
Нет данных для отображения
+ ${scoreStr ? `
${scoreStr}
` : ''} + `; return; } - contentEl.innerHTML = values - .map((v) => `
${this.escapeHtml(v)}
`) - .join(''); + contentEl.innerHTML = ` + ${values.map((v) => `
${this.escapeHtml(v)}
`).join('')} + ${scoreStr ? `
${scoreStr}
` : ''} + `; } else { const fwdVals = getWordFieldValues(word, forwardFields); const bwdVals = getWordFieldValues(word, backwardFields); @@ -151,6 +174,7 @@ export class TrainingPage { ? bwdVals.map((v) => `
${this.escapeHtml(v)}
`).join('') : '
'}
+ ${scoreStr ? `
${scoreStr}
` : ''} `; } } @@ -189,7 +213,9 @@ export class TrainingPage { const word = this.words[current]; if (!word) return; - this.renderSide(word, this.forwardFields, this.backwardFields, true); + const stat = this.cardSet.set[current]; + + this.renderSide(word, this.forwardFields, this.backwardFields, true, stat); this.renderActions(true); }