// 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 = ""; // Cache common DOM elements const mainContainer = document.querySelector('main'); const searchContainer = document.querySelector('search'); const footer = document.querySelector('footer'); console.log(mainContainer, searchContainer, footer); function viewSearch() { // Hide the main container and show the search container mainContainer.style.display = 'none'; searchContainer.style.display = 'block'; } function viewMain() { // Hide the search container and show the main container searchContainer.style.display = 'none'; mainContainer.style.display = 'block'; } // 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 += '
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 : '/'); }); }); 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; // Mark the clicked item as currently playing. this.closest('.file-item').classList.add('currently-playing'); startPlaying(relUrl); // Delay preloading to avoid blocking playback. setTimeout(preload_audio, 1000); } 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'); const highlight = this.getAttribute('highlight'); fetch(url) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(data => { // Highlight words in the transcript (substring match, case-insensitive) if (highlight) { // Escape special regex chars const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Split on whitespace, escape each word, and join with | const words = highlight .trim() .split(/\s+/) .map(escapeRegExp) .filter(Boolean); if (words.length) { const regex = new RegExp(`(${words.join('|')})`, 'gi'); data = data.replace(regex, '$1'); } } 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'; }); }); }); // Handle back/forward navigation. window.addEventListener('popstate', function (event) { const subpath = event.state ? event.state.subpath : ''; loadDirectory(subpath); }); } // End of attachEventListeners function // 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); }); 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); }); } 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