From 1ab023c2ac4b2b6599a12825a7d079d2f365c18e Mon Sep 17 00:00:00 2001 From: "thomas.kopp" Date: Wed, 1 Apr 2026 14:18:04 +0200 Subject: [PATCH] feat: transcript modal with markdown rendering, delete button, remove preview section --- frontend/app.js | 57 +++++++++++++++++++++++++--------- frontend/index.html | 75 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 509153d..0943af9 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1,11 +1,16 @@ const btn = document.getElementById('record-btn'); const statusText = document.getElementById('status-text'); const headerStatus = document.getElementById('header-status'); -const preview = document.getElementById('preview'); const instructionsEl = document.getElementById('instructions'); const transcriptList = document.getElementById('transcript-list'); const userChip = document.getElementById('user-chip'); const logoutBtn = document.getElementById('logout-btn'); +const modal = document.getElementById('modal'); +const modalTitle = document.getElementById('modal-title'); +const modalBody = document.getElementById('modal-body'); +const modalOpenBtn = document.getElementById('modal-open-btn'); +const modalCloseBtn = document.getElementById('modal-close-btn'); +let _modalPath = null; const STATUS_LABELS = { idle: 'Bereit', @@ -36,6 +41,30 @@ logoutBtn.addEventListener('click', () => { }); }); +function openModal(filename, path) { + _modalPath = path; + modalTitle.textContent = filename.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-\d{4}-/, ''); + modalBody.innerHTML = ''; + modal.classList.remove('hidden'); + apiFetch(`/transcripts/${encodeURIComponent(filename)}`) + .then(r => r.text()) + .then(md => { + modalBody.innerHTML = DOMPurify.sanitize(marked.parse(md)); + }); +} + +function closeModal() { + modal.classList.add('hidden'); + _modalPath = null; +} + +modalCloseBtn.addEventListener('click', closeModal); +modal.querySelector('.modal-backdrop').addEventListener('click', closeModal); +document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); +modalOpenBtn.addEventListener('click', () => { + if (_modalPath) apiFetch('/open', { method: 'POST', body: JSON.stringify({ path: _modalPath }) }); +}); + instructionsEl.addEventListener('input', async () => { await apiFetch('/instructions', { method: 'POST', @@ -58,8 +87,6 @@ btn.addEventListener('click', async () => { if (data.action === 'started') { setStatus('recording'); } else if (data.action === 'reset') { - preview.textContent = 'Noch keine Aufnahme verarbeitet.'; - preview.classList.remove('has-content'); setStatus('idle'); } }); @@ -70,18 +97,12 @@ function connectWs() { ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.event === 'processing') setStatus('processing'); - if (msg.event === 'transcribed' || msg.event === 'refined') { - const text = msg.raw || msg.markdown || ''; - preview.textContent = text; - preview.classList.add('has-content'); - } if (msg.event === 'saved') { setStatus('idle'); loadTranscripts(); } if (msg.event === 'error') { setStatus('error'); - preview.textContent = `Fehler: ${msg.message}`; } }; ws.onclose = () => setTimeout(connectWs, 2000); @@ -104,13 +125,19 @@ async function loadTranscripts() { meta.className = 'meta'; meta.textContent = `${Math.round(t.size / 1024 * 10) / 10} KB`; - div.append(name, meta); - div.addEventListener('click', () => { - apiFetch('/open', { - method: 'POST', - body: JSON.stringify({ path: t.path }), - }); + div.addEventListener('click', () => openModal(t.filename, t.path)); + + const delBtn = document.createElement('button'); + delBtn.className = 'del-btn'; + delBtn.title = 'Löschen'; + delBtn.innerHTML = ''; + delBtn.addEventListener('click', async (e) => { + e.stopPropagation(); + await apiFetch(`/transcripts/${encodeURIComponent(t.filename)}`, { method: 'DELETE' }); + loadTranscripts(); }); + + div.append(name, meta, delBtn); return div; }) ); diff --git a/frontend/index.html b/frontend/index.html index 58bb770..4455148 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -107,14 +107,6 @@ } textarea:focus { border-color: var(--yellow); } textarea::placeholder { color: var(--muted); } - .preview-section { display: flex; flex-direction: column; gap: 8px; } - #preview { - background: var(--surface); border: 1px solid var(--border); - border-radius: 8px; padding: 16px; - font-size: 0.85rem; line-height: 1.6; color: var(--muted); - min-height: 60px; white-space: pre-wrap; word-break: break-word; - } - #preview.has-content { color: var(--text); } .transcripts-section { display: flex; flex-direction: column; gap: 8px; } #transcript-list { display: flex; flex-direction: column; gap: 6px; } .transcript-item { @@ -125,6 +117,48 @@ } .transcript-item:hover { border-color: var(--red); } .transcript-item .meta { color: var(--muted); font-size: 0.75rem; } + .modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; } + .modal.hidden { display: none; } + .modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.7); } + .modal-panel { + position: relative; z-index: 1; + background: var(--surface); border: 1px solid var(--border); border-radius: 10px; + width: min(800px, 95vw); max-height: 85vh; + display: flex; flex-direction: column; + } + .modal-header { + display: flex; align-items: center; justify-content: space-between; + padding: 14px 18px; border-bottom: 1px solid var(--border); + flex-shrink: 0; + } + .modal-title { font-size: 0.9rem; font-weight: 600; } + .modal-actions { display: flex; gap: 8px; } + .modal-btn { + background: none; border: 1px solid var(--border); color: var(--muted); + border-radius: 6px; padding: 4px 8px; cursor: pointer; font-family: inherit; + font-size: 0.85rem; display: flex; align-items: center; + transition: border-color 0.15s, color 0.15s; + } + .modal-btn:hover { border-color: var(--red); color: var(--red); } + .modal-body { + padding: 20px 24px; overflow-y: auto; flex: 1; + font-size: 0.9rem; line-height: 1.7; color: var(--text); + } + .modal-body h1,.modal-body h2,.modal-body h3 { margin: 1em 0 0.4em; font-weight: 600; } + .modal-body h1 { font-size: 1.3rem; } + .modal-body h2 { font-size: 1.1rem; } + .modal-body p { margin: 0 0 0.8em; } + .modal-body ul,.modal-body ol { padding-left: 1.5em; margin: 0 0 0.8em; } + .modal-body code { background: var(--surface2); padding: 2px 5px; border-radius: 3px; font-size: 0.85em; } + .modal-body pre { background: var(--surface2); padding: 12px; border-radius: 6px; overflow-x: auto; margin: 0 0 0.8em; } + .modal-body pre code { background: none; padding: 0; } + .modal-body hr { border: none; border-top: 1px solid var(--border); margin: 1em 0; } + .del-btn { + background: none; border: none; color: var(--muted); cursor: pointer; + padding: 4px; border-radius: 4px; display: flex; align-items: center; + transition: color 0.15s; flex-shrink: 0; + } + .del-btn:hover { color: var(--red); } @@ -155,16 +189,31 @@ > -
- -
Noch keine Aufnahme verarbeitet.
-
-
+ + + +