// Common functionality // Initialize on page load document.addEventListener('DOMContentLoaded', () => { // Display recent videos in the footer on page load loadFooterRecentVideos(); // Handle theme switching const themeItems = document.querySelectorAll('.theme-item'); themeItems.forEach(item => { item.addEventListener('click', () => { const theme = item.dataset.theme; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); }); }); // Apply saved theme from localStorage if available const savedTheme = localStorage.getItem('theme'); if (savedTheme) { document.documentElement.setAttribute('data-theme', savedTheme); } // Handle global search const searchButton = document.getElementById('global-search-button'); const searchInput = document.getElementById('global-search'); if (searchButton && searchInput) { searchButton.addEventListener('click', () => { handleGlobalSearch(); }); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleGlobalSearch(); } }); } }); // Format seconds to HH:MM:SS format function formatTime(seconds) { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); if (hours > 0) { return `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } else { return `${mins}:${secs.toString().padStart(2, '0')}`; } } // Error handling function function handleError(error) { console.error('Error:', error); return ``; } // Toast notification function function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `alert alert-${type} fixed bottom-4 right-4 max-w-xs z-50 shadow-lg`; // Different icon based on type let icon = ''; switch(type) { case 'success': icon = ` `; break; case 'warning': icon = ` `; break; case 'error': icon = ` `; break; default: // info icon = ` `; } toast.innerHTML = ` ${icon} ${message}
`; document.body.appendChild(toast); // Auto-dismiss after 3 seconds setTimeout(() => { toast.classList.add('opacity-0', 'transition-opacity', 'duration-500'); setTimeout(() => toast.remove(), 500); }, 3000); } // Extract video ID from YouTube URL function extractVideoId(url) { const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; const match = url.match(regExp); return (match && match[7].length === 11) ? match[7] : null; } // Handle global search function handleGlobalSearch() { const searchInput = document.getElementById('global-search'); const searchTerm = searchInput.value.trim(); if (searchTerm) { // Get modal elements const searchModal = document.getElementById('search-results-modal'); const searchResultsContainer = document.getElementById('search-results-container'); const modalTitle = searchModal.querySelector('h3'); // Update modal title with search term modalTitle.textContent = `Search Results for "${searchTerm}"`; // Show the modal searchModal.showModal(); // Clear previous results and show loading state searchResultsContainer.innerHTML = `
Searching...
`; // Fetch search results from API fetch(`/api/video/search?query=${encodeURIComponent(searchTerm)}&limit=10`) .then(response => { if (!response.ok) { throw new Error('Failed to perform search'); } return response.json(); }) .then(results => { if (results && results.length > 0) { // Group results by video const videoGroups = {}; // First pass: collect all video IDs that need titles const videoIds = []; results.forEach(result => { const videoId = result.segment.video_id; if (!videoGroups[videoId]) { videoGroups[videoId] = { videoId: videoId, title: `Video ${videoId}`, // Default title, will be updated segments: [] }; // Add to list of IDs to fetch titles for videoIds.push(videoId); } videoGroups[videoId].segments.push(result); }); // If we have video IDs, fetch their proper titles const videoPromises = videoIds.map(videoId => { return fetch(`/api/video/info/${videoId}`) .then(response => response.ok ? response.json() : null) .then(videoInfo => { if (videoInfo && videoInfo.title && videoGroups[videoId]) { videoGroups[videoId].title = videoInfo.title; } }) .catch(error => console.error(`Error fetching video info for ${videoId}:`, error)); }); // After all video titles are fetched, continue with rendering Promise.all(videoPromises).then(() => { // Generate results HTML const resultsHTML = Object.values(videoGroups).map(group => { const segmentsHTML = group.segments.map(result => { return `
${formatTime(result.segment.start)}

${result.segment.text}

Score: ${(result.score * 100).toFixed(1)}%
`; }).join(''); return `
Thumbnail

${group.title}

${segmentsHTML}
`; }).join(''); // Update search results container searchResultsContainer.innerHTML = resultsHTML; }); } else { // No results found - display immediately, no need to wait for titles searchResultsContainer.innerHTML = `
No results found. Try a different search term or process more videos.
`; } }) .catch(error => { console.error('Search error:', error); searchResultsContainer.innerHTML = `
Error performing search: ${error.message}
`; }); } else { // Show notification if search is empty showToast('Please enter a search term', 'warning'); } } // Load recent videos into the footer from the API function loadFooterRecentVideos() { const footerRecentVideos = document.getElementById('footer-recent-videos'); if (!footerRecentVideos) return; // Show loading state footerRecentVideos.innerHTML = '

Loading recent videos...

'; // Fetch recent videos from server API fetch('/api/video/recent?limit=3') .then(response => { if (!response.ok) { throw new Error('Failed to fetch recent videos'); } return response.json(); }) .then(videos => { if (videos && videos.length > 0) { // Generate HTML for recent videos const videoLinks = videos.map(video => { return ` ${video.title || `Video ${video.video_id}`} `; }).join(''); // Add videos to the footer footerRecentVideos.innerHTML = videoLinks; } else { footerRecentVideos.innerHTML = '

No recent videos

'; } }) .catch(error => { console.error('Error loading footer videos:', error); footerRecentVideos.innerHTML = '

Failed to load recent videos

'; }); }