// Define global variables to track music files and the current index. let currentMusicFiles = []; // Array of objects with at least { path, index } let currentMusicIndex = -1; // Index of the current music file let currentTrackPath = ""; // Helper function: decode each segment then re-encode to avoid double encoding. function encodeSubpath(subpath) { if (!subpath) return ''; return subpath .split('/') .map(segment => encodeURIComponent(decodeURIComponent(segment))) .join('/'); } // Global variable for gallery images (updated from current folder) let currentGalleryImages = []; function paintFile() { // Highlight the currently playing file if (currentTrackPath) { const currentMusicFile = currentMusicFiles.find(file => file.path === currentTrackPath); if (currentMusicFile) { const currentMusicFileElement = document.querySelector(`.play-file[data-url="${currentMusicFile.path}"]`); if (currentMusicFileElement) { const fileItem = currentMusicFileElement.closest('.file-item'); fileItem.classList.add('currently-playing'); // setTimeout(() => { // fileItem.scrollIntoView({ block: "center", inline: "nearest" }); // }, 300); } } } } function renderContent(data) { // Render breadcrumbs let breadcrumbHTML = ''; data.breadcrumbs.forEach((crumb, index) => { breadcrumbHTML += `${crumb.name}`; if (index < data.breadcrumbs.length - 1) { breadcrumbHTML += `​>`; } }); document.getElementById('breadcrumbs').innerHTML = breadcrumbHTML; // Check for image-only directory (no subdirectories, at least one file, all images) const isImageOnly = data.directories.length === 0 && data.files.length > 0 && data.files.every(file => file.file_type === 'image'); let contentHTML = ''; if (isImageOnly) { // Display thumbnails grid contentHTML += '
'; data.files.forEach(file => { const thumbUrl = `${file.path}?thumbnail=true`; contentHTML += `
${file.name}
`; }); contentHTML += '
'; } else { share_link = ''; // Render directories normally if (data.directories.length > 0) { const areAllShort = data.directories.every(dir => dir.name.length <= 10) && data.files.length === 0; if (areAllShort && data.breadcrumbs.length !== 1) { contentHTML += '
'; data.directories.forEach(dir => { if (admin_enabled && data.breadcrumbs.length != 1) { share_link = `⚙️`; } contentHTML += `
📁 ${dir.name} ${share_link}
`; }); contentHTML += '
'; } else { contentHTML += ''; } } // Render files (including music and non-image files) currentMusicFiles = []; if (data.files.length > 0) { contentHTML += ''; } } // Insert generated content document.getElementById('content').innerHTML = contentHTML; // Attach event listeners for directories document.querySelectorAll('.directory-item').forEach(item => { item.addEventListener('click', function(e) { if (!e.target.closest('a')) { const anchor = item.querySelector('a.directory-link'); if (anchor) anchor.click(); } }); }); // Attach event listeners for file items (including images) if (isImageOnly) { document.querySelectorAll('.image-item').forEach(item => { item.addEventListener('click', function(e) { if (!e.target.closest('a')) { const anchor = item.querySelector('a.image-link'); if (anchor) anchor.click(); } }); }); } else { document.querySelectorAll('li.file-item').forEach(item => { item.addEventListener('click', function(e) { if (!e.target.closest('a')) { const anchor = item.querySelector('a.play-file'); if (anchor) anchor.click(); } }); }); } // Update gallery images for lightbox or similar currentGalleryImages = data.files .filter(f => f.file_type === 'image') .map(f => f.path); attachEventListeners(); } // Fetch directory data from the API. function loadDirectory(subpath) { const encodedPath = encodeSubpath(subpath); const apiUrl = '/api/path/' + encodedPath; return fetch(apiUrl) .then(response => response.json()) .then(data => { renderContent(data); if (data.playfile) { const playFileLink = document.querySelector(`.play-file[data-url="${data.playfile}"]`); if (playFileLink) { playFileLink.click(); } } paintFile(); return data; // return data for further chaining }) .catch(error => { console.error('Error loading directory:', error); document.getElementById('content').innerHTML = `

Error loading directory!

${error}

`; throw error; // Propagate the error }); } // preload the next audio file function preload_audio() { // Prefetch the next file by triggering the backend diskcache. if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; const nextMediaUrl = '/media/' + nextFile.path; // Use a HEAD request so that the backend reads and caches the file without returning the full content. fetch(nextMediaUrl, { method: 'HEAD', headers: { 'X-Cache-Request': 'true' } }); } } // Attach event listeners for directory, breadcrumb, file, and transcript links. function attachEventListeners() { // Directory link clicks. document.querySelectorAll('.directory-link').forEach(link => { link.addEventListener('click', function (event) { event.preventDefault(); const newPath = this.getAttribute('data-path'); loadDirectory(newPath); history.pushState({ subpath: newPath }, '', newPath ? '/path/' + newPath : '/'); // Scroll to the top of the page after click: window.scrollTo({ top: 0, behavior: 'smooth' }); }); }); // Breadcrumb link clicks. document.querySelectorAll('.breadcrumb-link').forEach(link => { link.addEventListener('click', function (event) { event.preventDefault(); const newPath = this.getAttribute('data-path'); loadDirectory(newPath); history.pushState({ subpath: newPath }, '', newPath ? '/path/' + newPath : '/'); }); }); // Cache common DOM elements const nowPlayingInfo = document.getElementById('nowPlayingInfo'); const audioPlayer = document.getElementById('globalAudio'); const playerButton = document.querySelector('.player-button'); const audioPlayerContainer = document.getElementById('audioPlayerContainer'); const footer = document.querySelector('footer'); // Global variable to store the current fetch's AbortController. let currentFetchController = null; document.querySelectorAll('.play-file').forEach(link => { link.addEventListener('click', async function (event) { event.preventDefault(); const { fileType, url: relUrl, index } = this.dataset; // Remove the class from all file items. document.querySelectorAll('.file-item').forEach(item => { item.classList.remove('currently-playing'); }); if (fileType === 'music') { // Update the current music index. currentMusicIndex = index !== undefined ? parseInt(index) : -1; // Display the audio player container. audioPlayerContainer.style.display = "block"; // Mark the clicked item as currently playing. this.closest('.file-item').classList.add('currently-playing'); // Abort any previous fetch if still running. if (currentFetchController) { currentFetchController.abort(); } currentFetchController = new AbortController(); // Pause the audio and clear its source. audioPlayer.pause(); audioPlayer.src = ''; // Set a timeout to display a loader message if needed. const loaderTimeout = setTimeout(() => { playerButton.innerHTML = playIcon; nowPlayingInfo.textContent = "Wird geladen..."; }, 500); footer.style.display = 'flex'; const mediaUrl = `/media/${relUrl}`; try { // Perform a HEAD request to verify media availability. const response = await fetch(mediaUrl, { method: 'HEAD', signal: currentFetchController.signal }); clearTimeout(loaderTimeout); if (response.status === 403) { nowPlayingInfo.textContent = "Fehler: Zugriff verweigert."; window.location.href = '/'; return; } else if (!response.ok) { nowPlayingInfo.textContent = `Fehler: Unerwarteter Status (${response.status}).`; console.error('Unexpected response status:', response.status); return; } // Set the media URL, load, and play the audio. audioPlayer.src = mediaUrl; audioPlayer.load(); await audioPlayer.play(); currentTrackPath = relUrl; playerButton.innerHTML = pauseIcon; // Process file path for display. const pathParts = relUrl.split('/'); const folderName = pathParts[pathParts.length - 2]; const fileName = pathParts.pop(); const pathStr = pathParts.join('/'); // Update Media Session metadata if available. if ('mediaSession' in navigator) { navigator.mediaSession.metadata = new MediaMetadata({ title: currentMusicFiles[currentMusicIndex].title, artist: folderName, artwork: [ { src: '/icons/logo-192x192.png', sizes: '192x192', type: 'image/png' } ] }); } nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') + '
' + fileName.replace('.mp3', '') + ''; // Delay preloading to avoid blocking playback. setTimeout(preload_audio, 1000); } catch (error) { if (error.name === 'AbortError') { console.log('Previous fetch aborted.'); } else { console.error('Error fetching media:', error); nowPlayingInfo.textContent = "Fehler: Netzwerkproblem oder ungültige URL."; } } } else if (fileType === 'image') { // Open the gallery modal for image files. openGalleryModal(relUrl); } else { // serve like a download window.location.href = `/media/${relUrl}`; } }); }); // Transcript icon clicks. document.querySelectorAll('.show-transcript').forEach(link => { link.addEventListener('click', function (event) { event.preventDefault(); const url = this.getAttribute('data-url'); fetch(url) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(data => { document.getElementById('transcriptContent').innerHTML = marked.parse(data); document.getElementById('transcriptModal').style.display = 'block'; }) .catch(error => { document.getElementById('transcriptContent').innerHTML = '

Error loading transcription.

'; document.getElementById('transcriptModal').style.display = 'block'; }); }); }); // create share icon clicks. document.querySelectorAll('.create-share').forEach(link => { link.addEventListener('click', function (event) { event.preventDefault(); const url = '/create_share/' + this.getAttribute('data-url'); console.log(url); fetch(url) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(data => { console.log(data); document.getElementById('transcriptContent').innerHTML = data; document.getElementById('transcriptModal').style.display = 'block'; }) .catch(error => { console.error('Error creating share:', error); document.getElementById('transcriptContent').innerHTML = "

You can't share this.

"; document.getElementById('transcriptModal').style.display = 'block'; }); }); }); } // Modal close logic for transcript modal. document.addEventListener('DOMContentLoaded', function () { const closeBtn = document.querySelector('#transcriptModal .close'); closeBtn.addEventListener('click', function () { document.getElementById('transcriptModal').style.display = 'none'; }); window.addEventListener('click', function (event) { if (event.target == document.getElementById('transcriptModal')) { document.getElementById('transcriptModal').style.display = 'none'; } }); // Load initial directory based on URL. let initialSubpath = ''; if (window.location.pathname.indexOf('/path/') === 0) { initialSubpath = window.location.pathname.substring(6); // remove "/path/" } loadDirectory(initialSubpath); }); // Handle back/forward navigation. window.addEventListener('popstate', function (event) { const subpath = event.state ? event.state.subpath : ''; loadDirectory(subpath); }); let isReloadButtonVisible = true; // Boolean to track the visibility of the reload button. function reloadDirectory() { if (!isReloadButtonVisible) return Promise.resolve(); // Extract the current path from the URL. let currentSubpath = ''; if (window.location.pathname.indexOf('/path/') === 0) { currentSubpath = window.location.pathname.substring(6); } // Return the promise so that we can chain actions after the directory has reloaded. return loadDirectory(currentSubpath) .then(() => { // Animate the reload button as before. const reloadBtn = document.querySelector('#reload-button'); const reloadBtnSVG = document.querySelector('#reload-button svg'); void reloadBtnSVG.offsetWidth; // Force reflow to reset the animation reloadBtnSVG.classList.add("rotate"); isReloadButtonVisible = false; setTimeout(() => { reloadBtnSVG.classList.remove("rotate"); reloadBtn.style.transition = 'opacity 0.5s ease'; reloadBtn.style.opacity = '1'; reloadBtn.style.pointer = 'cursor'; isReloadButtonVisible = true; }, 10000); }); } if ('mediaSession' in navigator) { // Handler for the play action navigator.mediaSession.setActionHandler('play', () => { document.getElementById('globalAudio').play(); }); // Handler for the pause action navigator.mediaSession.setActionHandler('pause', () => { document.getElementById('globalAudio').pause(); }); // Handler for the previous track action navigator.mediaSession.setActionHandler('previoustrack', () => { if (currentMusicIndex > 0) { const prevFile = currentMusicFiles[currentMusicIndex - 1]; const prevLink = document.querySelector(`.play-file[data-url="${prevFile.path}"]`); if (prevLink) { prevLink.click(); } } }); // Handler for the next track action navigator.mediaSession.setActionHandler('nexttrack', () => { if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`); if (nextLink) { nextLink.click(); } } }); } document.getElementById('globalAudio').addEventListener('ended', () => { reloadDirectory().then(() => { // If we had a track playing, try to find it in the updated list. if (currentTrackPath) { const newIndex = currentMusicFiles.findIndex(file => file.path === currentTrackPath); currentMusicIndex = newIndex; } // Now, if there's a next track, auto-play it. if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`); if (nextLink) { nextLink.click(); } } }).catch(error => { console.error('Error during reload:', error); }); }); // document.addEventListener("DOMContentLoaded", function() { // // Automatically reload every 5 minutes (300,000 milliseconds) // setInterval(reloadDirectory, 300000); // }); function syncThemeColor() { const cssVar = getComputedStyle(document.documentElement) .getPropertyValue('--dark-background') .trim(); if (!cssVar) return; // sync the theme‑color meta tag document.querySelector('meta[name="theme-color"]') .setAttribute('content', cssVar); // apply fill to every inside elements with .icon-color document .querySelectorAll('.icon-color svg') .forEach(svg => svg.setAttribute('fill', cssVar)); } function copyTokenUrl(url) { if (navigator.clipboard && window.isSecureContext) { // Modern approach navigator.clipboard.writeText(url) .then(() => { alert('Token-URL in die Zwischenablage kopiert!'); }) .catch(err => { console.error('Fehler beim Kopieren: ', err); }); } else { // Fallback for older browsers const textarea = document.createElement('textarea'); textarea.value = url; textarea.style.position = 'fixed'; // Verhindert Scrollen textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { document.execCommand('copy'); alert('Token-URL in die Zwischenablage kopiert!'); } catch (err) { console.error('Fallback: Kopieren fehlgeschlagen', err); } document.body.removeChild(textarea); } } document.addEventListener('DOMContentLoaded', syncThemeColor);