fix speech view
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1_8)">
|
||||||
|
<rect width="256" height="256" rx="50" fill="#00C8B3"/>
|
||||||
|
<path d="M134.6 179.85C134.9 178.65 135.1 177.35 135.2 175.95C135.4 174.45 135.5 172.95 135.5 171.45C135.5 170.45 135.5 168.25 135.5 164.85C135.5 161.45 135.5 157.3 135.5 152.4C135.5 147.4 135.5 142 135.5 136.2C135.5 130.4 135.5 124.6 135.5 118.8C135.5 112.9 135.5 107.4 135.5 102.3C135.5 97.2 135.5 92.8 135.5 89.1C135.5 85.4 135.5 82.9 135.5 81.6C135.5 78.7 135.4 76.3 135.2 74.4C135 72.4 134.85 71.1 134.75 70.5H148.55C148.55 71.1 148.45 72.4 148.25 74.4C148.05 76.4 147.95 78.8 147.95 81.6C147.95 83 147.95 85.5 147.95 89.1C147.95 92.6 147.95 96.9 147.95 102C147.95 107 147.95 112.4 147.95 118.2C147.95 123.9 147.95 129.55 147.95 135.15C147.95 140.75 147.95 145.95 147.95 150.75C147.95 155.45 147.95 159.35 147.95 162.45C147.95 165.55 147.95 167.45 147.95 168.15C152.65 165.95 157.65 163.05 162.95 159.45C168.25 155.85 173.4 151.65 178.4 146.85C183.5 142.05 187.95 136.85 191.75 131.25L198.8 141.45C194.5 147.25 189.45 152.8 183.65 158.1C177.95 163.4 172 168.15 165.8 172.35C159.6 176.55 153.55 180.1 147.65 183C146.45 183.7 145.4 184.35 144.5 184.95C143.7 185.55 143.05 186.05 142.55 186.45L134.6 179.85ZM65.9 179.1C72.2 174.6 77.55 169.15 81.95 162.75C86.35 156.35 89.7 149.5 92 142.2C93.1 138.8 93.95 134.55 94.55 129.45C95.15 124.35 95.6 118.95 95.9 113.25C96.2 107.45 96.35 101.85 96.35 96.45C96.45 90.95 96.5 86.1 96.5 81.9C96.5 79.7 96.4 77.75 96.2 76.05C96 74.25 95.75 72.55 95.45 70.95H109.25C109.15 71.55 109.05 72.45 108.95 73.65C108.85 74.75 108.75 76 108.65 77.4C108.55 78.8 108.5 80.25 108.5 81.75C108.5 85.95 108.45 90.9 108.35 96.6C108.25 102.2 108 108.1 107.6 114.3C107.3 120.4 106.85 126.2 106.25 131.7C105.75 137.2 104.95 141.8 103.85 145.5C101.65 153.5 98.2 161.05 93.5 168.15C88.9 175.25 83.45 181.45 77.15 186.75L65.9 179.1Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_8">
|
||||||
|
<rect width="256" height="256" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||||
|
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||||
|
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||||
|
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" translate="no">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" href="/icon-192.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#7c3aed" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<title>JapLearn</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-CzXGdRG9.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-XX_s0Oss.css">
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<header class="app-header">
|
||||||
|
<h1>JapLearn</h1>
|
||||||
|
<p>Интервальные повторения японских слов</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="page-container" class="app-content">
|
||||||
|
<!-- Страницы рендерятся сюда динамически -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav id="app-nav" class="app-nav">
|
||||||
|
<button class="nav-btn active" data-page="words">📚 Слова</button>
|
||||||
|
<button class="nav-btn" data-page="sets">🗂️ Колоды</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "JapLearn",
|
||||||
|
"short_name": "JapLearn",
|
||||||
|
"description": "Интервальные повторения японских слов",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#7c3aed",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"name":"JapLearn","short_name":"JapLearn","description":"Интервальные повторения японских слов","start_url":"/","display":"standalone","background_color":"#ffffff","theme_color":"#7c3aed","lang":"en","scope":"/","orientation":"portrait","icons":[{"src":"icon-192.png","sizes":"192x192","type":"image/png"},{"src":"icon-512.png","sizes":"512x512","type":"image/png"},{"src":"icon-512.png","sizes":"512x512","type":"image/png","purpose":"any maskable"}]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js', { scope: '/' })})}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(s[a])return;let o={};const c=e=>i(e,a),l={module:{uri:a},exports:o,require:c};s[a]=Promise.all(n.map(e=>l[e]||c(e))).then(e=>(r(...e),o))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"index.html",revision:"f63f75d4eabe50ba7027246e2712fd0f"},{url:"icons.svg",revision:"3b4fcfcf393eca4d264dca4a4663bc37"},{url:"icon-512.png",revision:"742f835d951738a122569da26fd5436c"},{url:"icon-192.png",revision:"ab94a1a8a8b47289c8a62e56929aa22c"},{url:"favicon.svg",revision:"94b5c7ec23607198a93dfd22a617c192"},{url:"assets/zstd-CV7N7xSg.wasm",revision:null},{url:"assets/wa-sqlite-async-DY3_ptqa.wasm",revision:null},{url:"assets/index-XX_s0Oss.css",revision:null},{url:"assets/index-CzXGdRG9.js",revision:null},{url:"assets/db-worker-DEWOyJFk.js",revision:null},{url:"favicon.svg",revision:"94b5c7ec23607198a93dfd22a617c192"},{url:"icon-192.png",revision:"ab94a1a8a8b47289c8a62e56929aa22c"},{url:"icon-512.png",revision:"742f835d951738a122569da26fd5436c"},{url:"manifest.webmanifest",revision:"a772ac7a933e1fc8361edca90e19a887"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
|
||||||
File diff suppressed because one or more lines are too long
+29
-15
@@ -7,7 +7,8 @@ import type { CardSetRecord } from './db-manager.ts';
|
|||||||
import { SetOrderMode } from './algorithm.ts';
|
import { SetOrderMode } from './algorithm.ts';
|
||||||
import { TrainingPage } from './training.ts';
|
import { TrainingPage } from './training.ts';
|
||||||
import type { WordItem } 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 API_BASE_URL = 'https://learning.micialware.ru';
|
||||||
const SYNC_ID_STORAGE_KEY = 'japlearn_sync_id';
|
const SYNC_ID_STORAGE_KEY = 'japlearn_sync_id';
|
||||||
@@ -15,7 +16,7 @@ const SYNC_ID_STORAGE_KEY = 'japlearn_sync_id';
|
|||||||
// ----- Элементы UI -----
|
// ----- Элементы UI -----
|
||||||
const container = document.getElementById('page-container') as HTMLElement;
|
const container = document.getElementById('page-container') as HTMLElement;
|
||||||
const navBtns = document.querySelectorAll<HTMLButtonElement>('.nav-btn');
|
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');
|
const wordEl = document.createElement('div');
|
||||||
wordEl.className = 'word-item';
|
wordEl.className = 'word-item';
|
||||||
|
|
||||||
let reading = word.key;
|
// В списке показываем всегда key, без подмены на reading
|
||||||
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 */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
wordEl.innerHTML = `
|
wordEl.innerHTML = `
|
||||||
<span class="word-key">${escapeHtml(reading)}</span>
|
<span class="word-key">${escapeHtml(word.key)}</span>
|
||||||
<span class="word-value">${escapeHtml(translation)}</span>
|
<span class="word-value">${escapeHtml(word.value)}</span>
|
||||||
`;
|
`;
|
||||||
|
wordEl.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showWordView(word);
|
||||||
|
});
|
||||||
wordsContainer.appendChild(wordEl);
|
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) {
|
async function startTraining(setId: number) {
|
||||||
@@ -266,7 +280,7 @@ async function startTraining(setId: number) {
|
|||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
id: set.id,
|
id: set.id,
|
||||||
name: set.name,
|
name: set.namclinee,
|
||||||
forward: set.forward,
|
forward: set.forward,
|
||||||
backward: set.backward,
|
backward: set.backward,
|
||||||
filter: set.filter,
|
filter: set.filter,
|
||||||
|
|||||||
@@ -599,6 +599,78 @@ body {
|
|||||||
opacity: 0.8;
|
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 {
|
.training-actions {
|
||||||
padding: 0.75rem 0;
|
padding: 0.75rem 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ function getWordFieldValues(word: WordItem, fields: string): string[] {
|
|||||||
.map((f) => {
|
.map((f) => {
|
||||||
if (f in directFields && directFields[f]) return directFields[f];
|
if (f in directFields && directFields[f]) return directFields[f];
|
||||||
if (f in word.more && word.more[f]) return word.more[f];
|
if (f in word.more && word.more[f]) return word.more[f];
|
||||||
|
// Если поле speech отсутствует, используем key
|
||||||
|
if (f === 'speech' && word.key) return word.key;
|
||||||
return '';
|
return '';
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user