Revemo always kana view
This commit is contained in:
@@ -22,3 +22,4 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
jwl.tar.gz
|
||||||
-10
@@ -95,16 +95,6 @@ function renderWordsList(words: Word[]) {
|
|||||||
let reading = word.key;
|
let reading = word.key;
|
||||||
let translation = word.value;
|
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 = `
|
li.innerHTML = `
|
||||||
<span class="word-key">${reading}</span>
|
<span class="word-key">${reading}</span>
|
||||||
<span class="word-value">${escapeHtml(translation)}</span>
|
<span class="word-value">${escapeHtml(translation)}</span>
|
||||||
|
|||||||
+8
-1
@@ -249,7 +249,6 @@ body {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
@@ -475,6 +474,14 @@ body {
|
|||||||
margin: 0.75rem 0;
|
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 {
|
.training-actions {
|
||||||
padding: 0.75rem 0;
|
padding: 0.75rem 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export async function uploadDatabase(
|
|||||||
console.log('[Sync] База данных успешно загружена.');
|
console.log('[Sync] База данных успешно загружена.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Sync] Ошибка загрузки:', err);
|
console.error('[Sync] Ошибка загрузки:', err);
|
||||||
|
console.log(err);
|
||||||
alert(`Ошибка загрузки: ${err instanceof Error ? err.message : String(err)}`);
|
alert(`Ошибка загрузки: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -67,6 +68,7 @@ export async function downloadDatabase(
|
|||||||
try {
|
try {
|
||||||
// 1. GET-запрос
|
// 1. GET-запрос
|
||||||
const url = `${apiBaseUrl.replace(/\/+$/, '')}/download/${encodeURIComponent(id)}`;
|
const url = `${apiBaseUrl.replace(/\/+$/, '')}/download/${encodeURIComponent(id)}`;
|
||||||
|
console.log(url);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
if (!response.ok) {
|
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 type { CardSetSettings, CardStatistics, WordData, WordOpenMode } from './algorithm.ts';
|
||||||
import { updateCardStat } from './db-manager.ts';
|
import { updateCardStat } from './db-manager.ts';
|
||||||
|
|
||||||
@@ -11,12 +11,6 @@ export interface WordItem {
|
|||||||
group_id: number;
|
group_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Извлекает значения полей из слова.
|
|
||||||
* Сначала ищет в основных полях (key, value, tags),
|
|
||||||
* потом в more (JSON-словарь).
|
|
||||||
* Если поле не найдено — пропускается (возвращается пустая строка, фильтруется).
|
|
||||||
*/
|
|
||||||
function getWordFieldValues(word: WordItem, fields: string): string[] {
|
function getWordFieldValues(word: WordItem, fields: string): string[] {
|
||||||
const directFields: Record<string, string> = {
|
const directFields: Record<string, string> = {
|
||||||
key: word.key,
|
key: word.key,
|
||||||
@@ -47,6 +41,8 @@ export class TrainingPage {
|
|||||||
private totalCards: number;
|
private totalCards: number;
|
||||||
private forwardFields: string;
|
private forwardFields: string;
|
||||||
private backwardFields: string;
|
private backwardFields: string;
|
||||||
|
private seenWords = new Set<number>();
|
||||||
|
private shownCount = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
@@ -81,7 +77,7 @@ export class TrainingPage {
|
|||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
<div class="training-header">
|
<div class="training-header">
|
||||||
<button id="btnTrainingBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
|
<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>
|
||||||
<div id="training-card" class="training-card">
|
<div id="training-card" class="training-card">
|
||||||
<div class="training-content" id="training-content">
|
<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 {
|
private renderCard(): void {
|
||||||
const result = this.cardSet.next();
|
const result = this.cardSet.next();
|
||||||
const word = this.words.find((w) => w.id === result.word.id);
|
const word = this.words.find((w) => w.id === result.word.id);
|
||||||
if (!word) return;
|
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!];
|
const stat = this.cardSet.set[this.cardSet.currentWordIndex!];
|
||||||
if (stat) {
|
if (stat) {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
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) =>
|
updateCardStat(stat.id, stat.score, now).catch((err) =>
|
||||||
console.error('[Training] Ошибка сохранения last_opened:', 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);
|
this.renderActions(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,19 +133,31 @@ export class TrainingPage {
|
|||||||
forwardFields: string,
|
forwardFields: string,
|
||||||
backwardFields: string,
|
backwardFields: string,
|
||||||
showBack: boolean,
|
showBack: boolean,
|
||||||
|
stat?: CardStatistics,
|
||||||
): void {
|
): void {
|
||||||
const contentEl = document.getElementById('training-content');
|
const contentEl = document.getElementById('training-content');
|
||||||
if (!contentEl) return;
|
if (!contentEl) return;
|
||||||
|
|
||||||
|
// Текущий балл слова (с учётом затухания)
|
||||||
|
let scoreStr = '';
|
||||||
|
if (stat) {
|
||||||
|
const cs = calculatedScore(stat);
|
||||||
|
scoreStr = `⚡ ${cs.toFixed(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!showBack) {
|
if (!showBack) {
|
||||||
const values = getWordFieldValues(word, forwardFields);
|
const values = getWordFieldValues(word, forwardFields);
|
||||||
if (values.length === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
contentEl.innerHTML = values
|
contentEl.innerHTML = `
|
||||||
.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`)
|
${values.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`).join('')}
|
||||||
.join('');
|
${scoreStr ? `<div class="training-score">${scoreStr}</div>` : ''}
|
||||||
|
`;
|
||||||
} else {
|
} else {
|
||||||
const fwdVals = getWordFieldValues(word, forwardFields);
|
const fwdVals = getWordFieldValues(word, forwardFields);
|
||||||
const bwdVals = getWordFieldValues(word, backwardFields);
|
const bwdVals = getWordFieldValues(word, backwardFields);
|
||||||
@@ -151,6 +174,7 @@ export class TrainingPage {
|
|||||||
? bwdVals.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`).join('')
|
? bwdVals.map((v) => `<div class="training-field">${this.escapeHtml(v)}</div>`).join('')
|
||||||
: '<div class="training-field" style="opacity:0.4;">—</div>'}
|
: '<div class="training-field" style="opacity:0.4;">—</div>'}
|
||||||
</div>
|
</div>
|
||||||
|
${scoreStr ? `<div class="training-score">${scoreStr}</div>` : ''}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +213,9 @@ export class TrainingPage {
|
|||||||
const word = this.words[current];
|
const word = this.words[current];
|
||||||
if (!word) return;
|
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);
|
this.renderActions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user