Revemo always kana view
This commit is contained in:
-10
@@ -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 = `
|
||||
<span class="word-key">${reading}</span>
|
||||
<span class="word-value">${escapeHtml(translation)}</span>
|
||||
|
||||
+8
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+42
-16
@@ -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<string, string> = {
|
||||
key: word.key,
|
||||
@@ -47,6 +41,8 @@ export class TrainingPage {
|
||||
private totalCards: number;
|
||||
private forwardFields: string;
|
||||
private backwardFields: string;
|
||||
private seenWords = new Set<number>();
|
||||
private shownCount = 0;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
@@ -81,7 +77,7 @@ export class TrainingPage {
|
||||
this.container.innerHTML = `
|
||||
<div class="training-header">
|
||||
<button id="btnTrainingBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
|
||||
<span style="font-size:0.85rem;color:var(--text);">слов: ${total}</span>
|
||||
<span style="font-size:0.85rem;color:var(--text);">уникальных: 0 / ${total}</span>
|
||||
</div>
|
||||
<div id="training-card" class="training-card">
|
||||
<div class="training-content" id="training-content">
|
||||
@@ -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 = '<div class="training-field" style="opacity:0.4;">Нет данных для отображения</div>';
|
||||
contentEl.innerHTML = `
|
||||
<div class="training-field" style="opacity:0.4;">Нет данных для отображения</div>
|
||||
${scoreStr ? `<div class="training-score">${scoreStr}</div>` : ''}
|
||||
`;
|
||||
return;
|
||||
}
|
||||
contentEl.innerHTML = values
|
||||
.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`)
|
||||
.join('');
|
||||
contentEl.innerHTML = `
|
||||
${values.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`).join('')}
|
||||
${scoreStr ? `<div class="training-score">${scoreStr}</div>` : ''}
|
||||
`;
|
||||
} else {
|
||||
const fwdVals = getWordFieldValues(word, forwardFields);
|
||||
const bwdVals = getWordFieldValues(word, backwardFields);
|
||||
@@ -151,6 +174,7 @@ export class TrainingPage {
|
||||
? bwdVals.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`).join('')
|
||||
: '<div class="training-field" style="opacity:0.4;">—</div>'}
|
||||
</div>
|
||||
${scoreStr ? `<div class="training-score">${scoreStr}</div>` : ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user