diff --git a/static/app.css b/static/app.css index c93a145..6d52f3e 100644 --- a/static/app.css +++ b/static/app.css @@ -184,6 +184,11 @@ a.show-transcript:hover { .highlight { background-color: yellow; } +#transcriptContent code.timecode-link { + cursor: pointer; + font-size: small; +} + a.create-share { text-decoration: none; diff --git a/static/app.js b/static/app.js index 96f681f..9bcc4ab 100644 --- a/static/app.js +++ b/static/app.js @@ -2,6 +2,7 @@ let currentMusicFiles = []; // Array of objects with at least { path, index } let currentMusicIndex = -1; // Index of the current music file let currentTrackPath = ""; +let activeTranscriptAudio = ""; // Audio file that matches the currently open transcript. // Check thumb availability in parallel; sequentially generate missing ones to avoid concurrent creation. function ensureFirstFivePreviews() { @@ -70,6 +71,79 @@ function encodeSubpath(subpath) { .join('/'); } +// Convert a timecode string like "00:17" or "01:02:03" into total seconds. +function parseTimecode(text) { + const parts = text.trim().split(':').map(part => parseInt(part, 10)); + if (parts.some(Number.isNaN) || parts.length < 2 || parts.length > 3) return null; + + const hasHours = parts.length === 3; + const [hours, minutes, seconds] = hasHours ? parts : [0, parts[0], parts[1]]; + + if (seconds < 0 || seconds > 59) return null; + if (minutes < 0 || (hasHours && minutes > 59)) return null; + if (hours < 0) return null; + + return hours * 3600 + minutes * 60 + seconds; +} + +// Add click handlers to transcript timecodes so they seek the matching audio. +function attachTranscriptTimecodes(audioPath) { + const container = document.getElementById('transcriptContent'); + const targetAudio = audioPath || activeTranscriptAudio; + if (!container || !targetAudio) return; + + container.querySelectorAll('code').forEach(codeEl => { + if (codeEl.closest('pre')) return; + const label = codeEl.textContent.trim(); + const seconds = parseTimecode(label); + if (seconds === null) return; + + codeEl.classList.add('timecode-link'); + codeEl.title = `Zu ${label} springen`; + codeEl.addEventListener('click', () => jumpToTimecode(seconds, targetAudio)); + }); +} + +// Ensure we load the right track (if needed) and jump to the requested time. +function jumpToTimecode(seconds, relUrl) { + if (!relUrl || seconds === null) return; + + const updateTrackState = () => { + currentTrackPath = relUrl; + const matchIdx = currentMusicFiles.findIndex(file => file.path === relUrl); + currentMusicIndex = matchIdx !== -1 ? matchIdx : -1; + }; + + const seekWhenReady = () => { + player.audio.currentTime = Math.min( + seconds, + Number.isFinite(player.audio.duration) ? player.audio.duration : seconds + ); + player.audio.play(); + player.updateTimeline(); + }; + + updateTrackState(); + + if (player.currentRelUrl === relUrl) { + if (player.audio.readyState >= 1) { + seekWhenReady(); + } else { + player.audio.addEventListener('loadedmetadata', seekWhenReady, { once: true }); + } + return; + } + + player.audio.addEventListener('loadedmetadata', seekWhenReady, { once: true }); + + const trackLink = document.querySelector(`.play-file[data-url="${relUrl}"]`); + if (trackLink) { + trackLink.click(); + } else { + player.loadTrack(relUrl); + } +} + // Global variable for gallery images (updated from current folder) let currentGalleryImages = []; @@ -231,7 +305,7 @@ function renderContent(data) { contentHTML += `
Error loading transcription.
'; document.getElementById('transcriptModal').style.display = 'block'; }); diff --git a/static/search.js b/static/search.js index f70839d..1c593c7 100644 --- a/static/search.js +++ b/static/search.js @@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', function() {Anzahl Downloads: ${file.hitcount}
${ file.performance_date !== undefined ? `Datum: ${file.performance_date}
` : ``} - ${ file.transcript_hits !== undefined ? `Treffer im Transkript: ${file.transcript_hits} 📄
` : ``} + ${ file.transcript_hits !== undefined ? `Treffer im Transkript: ${file.transcript_hits} 📄
` : ``} `; resultsDiv.appendChild(card); @@ -162,4 +162,4 @@ function syncThemeColor() { } // sync once on load - document.addEventListener('DOMContentLoaded', syncThemeColor); \ No newline at end of file + document.addEventListener('DOMContentLoaded', syncThemeColor);