fix speech view

This commit is contained in:
2026-07-03 13:06:14 +03:00
parent 731951ca75
commit e00e524b7a
19 changed files with 462 additions and 15 deletions
+29 -15
View File
@@ -7,7 +7,8 @@ import type { CardSetRecord } from './db-manager.ts';
import { SetOrderMode } from './algorithm.ts';
import { TrainingPage } from './training.ts';
import type { WordItem } from './training.ts';
import { renderWordView } from './word-view.ts';
import type { Word } from './db-manager.ts';
// ----- Конфигурация -----
const API_BASE_URL = 'https://learning.micialware.ru';
const SYNC_ID_STORAGE_KEY = 'japlearn_sync_id';
@@ -15,7 +16,7 @@ const SYNC_ID_STORAGE_KEY = 'japlearn_sync_id';
// ----- Элементы UI -----
const container = document.getElementById('page-container') as HTMLElement;
const navBtns = document.querySelectorAll<HTMLButtonElement>('.nav-btn');
let currentPage: 'words' | 'sets' | 'training' = 'words';
let currentPage: 'words' | 'sets' | 'training' | 'wordview' = 'words';
// ----- Вспомогательные функции -----
@@ -135,20 +136,15 @@ function renderWordGroups(grouped: GroupedWords[]) {
const wordEl = document.createElement('div');
wordEl.className = 'word-item';
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 */ }
}
// В списке показываем всегда key, без подмены на reading
wordEl.innerHTML = `
<span class="word-key">${escapeHtml(reading)}</span>
<span class="word-value">${escapeHtml(translation)}</span>
<span class="word-key">${escapeHtml(word.key)}</span>
<span class="word-value">${escapeHtml(word.value)}</span>
`;
wordEl.addEventListener('click', (e) => {
e.stopPropagation();
showWordView(word);
});
wordsContainer.appendChild(wordEl);
}
@@ -243,6 +239,24 @@ function showSetDetail(set: CardSetRecord, listItem: HTMLLIElement) {
`;
}
// ----- Просмотр слова -----
function showWordView(word: Word) {
hideNav();
currentPage = 'wordview';
renderWordView(
container,
word,
{
onBack: () => {
showNav();
switchPage('words');
},
},
);
}
// ----- Тренировка -----
async function startTraining(setId: number) {
@@ -266,7 +280,7 @@ async function startTraining(setId: number) {
const settings = {
id: set.id,
name: set.name,
name: set.namclinee,
forward: set.forward,
backward: set.backward,
filter: set.filter,
+72
View File
@@ -599,6 +599,78 @@ body {
opacity: 0.8;
}
/* ──── Просмотр слова ──── */
.word-view-header {
padding: 0.5rem 0;
}
.word-view-card {
flex: 1;
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 16px;
padding: 1.5rem;
box-shadow: var(--shadow);
}
.word-view-section {
padding: 0.25rem 0;
}
.word-view-label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.5rem;
}
.word-view-field {
font-size: 1.3rem;
font-weight: 600;
color: var(--text-h);
padding: 0.25rem 0;
line-height: 1.4;
}
.word-view-field-back {
color: var(--accent);
}
.word-view-divider {
border: none;
border-top: 1px solid var(--border);
margin: 0.75rem 0;
}
.word-view-more-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
gap: 8px;
}
.word-view-more-key {
font-size: 0.8rem;
font-weight: 500;
color: var(--text);
flex-shrink: 0;
}
.word-view-more-value {
font-size: 0.9rem;
color: var(--text-h);
text-align: right;
word-break: break-word;
}
.word-view-actions {
padding: 0.75rem 0;
}
.training-actions {
padding: 0.75rem 0;
}
+2
View File
@@ -24,6 +24,8 @@ function getWordFieldValues(word: WordItem, fields: string): string[] {
.map((f) => {
if (f in directFields && directFields[f]) return directFields[f];
if (f in word.more && word.more[f]) return word.more[f];
// Если поле speech отсутствует, используем key
if (f === 'speech' && word.key) return word.key;
return '';
})
.filter(Boolean);
+77
View File
@@ -0,0 +1,77 @@
import type { Word } from './db-manager.ts';
export interface ViewCallbacks {
onBack: () => void;
}
function parseMore(more: string | null): Record<string, string> {
if (!more) return {};
try {
return JSON.parse(more);
} catch {
return {};
}
}
/**
* Показывает страницу просмотра одного слова.
* Отображает стандартные поля (key, value, tags) + все значения из more.
*/
export function renderWordView(
container: HTMLElement,
word: Word,
callbacks: ViewCallbacks,
): void {
const more = parseMore(word.more);
container.innerHTML = `
<div class="word-view-header">
<button id="btnViewBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
</div>
<div class="word-view-card">
<div class="word-view-section">
<div class="word-view-label">Японское слово</div>
<div class="word-view-field">${escapeHtml(word.key)}</div>
</div>
<div class="word-view-divider"></div>
<div class="word-view-section">
<div class="word-view-label">Перевод</div>
<div class="word-view-field">${escapeHtml(word.value)}</div>
</div>
<div class="word-view-divider"></div>
<div class="word-view-section">
<div class="word-view-label">Теги</div>
<div class="word-view-field" style="font-size:0.9rem;color:var(--accent);">
${word.tags ? escapeHtml(word.tags) : '—'}
</div>
</div>
${Object.keys(more).length > 0 ? `
<div class="word-view-divider"></div>
<div class="word-view-section">
<div class="word-view-label">Дополнительные данные</div>
${Object.entries(more).map(([k, v]) => `
<div class="word-view-more-row">
<span class="word-view-more-key">${escapeHtml(k)}</span>
<span class="word-view-more-value">${escapeHtml(v)}</span>
</div>
`).join('')}
</div>
` : ''}
</div>
`;
document.getElementById('btnViewBack')?.addEventListener('click', () => {
callbacks.onBack();
});
}
function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}