// KV-Tube Main JavaScript - YouTube Clone
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('searchInput');
const resultsArea = document.getElementById('resultsArea');
if (searchInput) {
searchInput.addEventListener('keypress', async (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const query = searchInput.value.trim();
if (query) {
// Check if on search page already, if not redirect
// Since we are SPA-ish, we just call searchYouTube
// But if we want a dedicated search page URL, we could do:
// window.history.pushState({}, '', `/?q=${encodeURIComponent(query)}`);
searchYouTube(query);
}
}
});
// Load trending on init
loadTrending();
}
// Init Theme
initTheme();
});
// Note: Global variables like currentCategory are defined below
let currentCategory = 'general';
let currentPage = 1;
let isLoading = false;
// --- UI Helpers ---
function renderSkeleton() {
// Generate 8 skeleton cards
return Array(8).fill(0).map(() => `
`).join('');
}
function renderNoContent(message = 'Try searching for something else', title = 'No videos found') {
return `
`;
}
// Search YouTube videos
async function searchYouTube(query) {
if (isLoading) return;
const resultsArea = document.getElementById('resultsArea');
const loadMoreArea = document.getElementById('loadMoreArea');
isLoading = true;
resultsArea.innerHTML = renderSkeleton();
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
if (data.error) {
resultsArea.innerHTML = ``;
return;
}
displayResults(data, false);
if (loadMoreArea) loadMoreArea.style.display = 'none';
} catch (error) {
console.error('Search error:', error);
resultsArea.innerHTML = ``;
} finally {
isLoading = false;
}
}
// Switch category
async function switchCategory(category, btn) {
if (isLoading) return;
// Update UI (Pills)
document.querySelectorAll('.yt-category-pill').forEach(b => b.classList.remove('active'));
if (btn && btn.classList) btn.classList.add('active');
// Update UI (Sidebar)
document.querySelectorAll('.yt-sidebar-item').forEach(item => {
item.classList.remove('active');
if (item.getAttribute('data-category') === category) {
item.classList.add('active');
}
});
// Reset state
currentCategory = category;
currentPage = 1;
window.currentPage = 1;
const resultsArea = document.getElementById('resultsArea');
resultsArea.innerHTML = renderSkeleton();
// Hide pagination while loading
const paginationArea = document.getElementById('paginationArea');
if (paginationArea) paginationArea.style.display = 'none';
// Load both videos and shorts with current category, sort, and region
await loadTrending(true);
// Also reload shorts to match category
if (typeof loadShorts === 'function') {
loadShorts();
}
// Render pagination
if (typeof renderPagination === 'function') {
renderPagination();
}
}
// Load more videos
async function loadMore() {
currentPage++;
await loadTrending(false);
}
// Load trending videos
async function loadTrending(reset = true) {
if (isLoading && reset) isLoading = false;
const resultsArea = document.getElementById('resultsArea');
const loadMoreArea = document.getElementById('loadMoreArea');
const loadMoreBtn = document.getElementById('loadMoreBtn');
if (!resultsArea) return; // Exit if not on home page
isLoading = true;
if (!reset && loadMoreBtn) {
loadMoreBtn.innerHTML = ' Loading...';
}
try {
// Get sort and region values from page (if available)
const sortValue = window.currentSort || 'month';
const regionValue = window.currentRegion || 'vietnam';
const response = await fetch(`/api/trending?category=${currentCategory}&page=${currentPage}&sort=${sortValue}®ion=${regionValue}`);
const data = await response.json();
if (data.error) {
console.error('Trending error:', data.error);
if (reset) {
resultsArea.innerHTML = renderNoContent(`Error: ${data.error}`, 'Something went wrong');
}
return;
}
if (reset) resultsArea.innerHTML = '';
if (data.length === 0) {
if (reset) {
resultsArea.innerHTML = renderNoContent();
}
const paginationArea = document.getElementById('paginationArea');
if (paginationArea) paginationArea.style.display = 'none';
} else {
displayResults(data, !reset);
const paginationArea = document.getElementById('paginationArea');
if (paginationArea) paginationArea.style.display = 'flex';
// Update pagination if function exists
if (typeof renderPagination === 'function') {
renderPagination();
}
}
} catch (e) {
console.error('Failed to load trending:', e);
if (reset) {
resultsArea.innerHTML = ``;
}
} finally {
isLoading = false;
}
}
// Display results with YouTube-style cards
function displayResults(videos, append = false) {
const resultsArea = document.getElementById('resultsArea');
if (!append) resultsArea.innerHTML = '';
if (videos.length === 0 && !append) {
resultsArea.innerHTML = renderNoContent();
return;
}
videos.forEach(video => {
const card = document.createElement('div');
card.className = 'yt-video-card';
card.innerHTML = `

${video.duration ? `
${video.duration}` : ''}
${video.uploader ? video.uploader.charAt(0).toUpperCase() : 'Y'}
`;
card.addEventListener('click', () => {
window.location.href = `/watch?v=${video.id}`;
});
resultsArea.appendChild(card);
});
}
// Format view count (YouTube style)
function formatViews(views) {
if (!views) return '0';
const num = parseInt(views);
if (num >= 1000000000) return (num / 1000000000).toFixed(1) + 'B';
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num.toLocaleString();
}
// Format date (YouTube style: "2 hours ago", "3 days ago", etc.)
function formatDate(dateStr) {
if (!dateStr) return 'Recently';
// Handle YYYYMMDD format
if (/^\d{8}$/.test(dateStr)) {
const year = dateStr.substring(0, 4);
const month = dateStr.substring(4, 6);
const day = dateStr.substring(6, 8);
dateStr = `${year}-${month}-${day}`;
}
const date = new Date(dateStr);
if (isNaN(date.getTime())) return 'Recently';
const now = new Date();
const diffMs = now - date;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
const diffWeek = Math.floor(diffDay / 7);
const diffMonth = Math.floor(diffDay / 30);
const diffYear = Math.floor(diffDay / 365);
if (diffYear > 0) return `${diffYear} year${diffYear > 1 ? 's' : ''} ago`;
if (diffMonth > 0) return `${diffMonth} month${diffMonth > 1 ? 's' : ''} ago`;
if (diffWeek > 0) return `${diffWeek} week${diffWeek > 1 ? 's' : ''} ago`;
if (diffDay > 0) return `${diffDay} day${diffDay > 1 ? 's' : ''} ago`;
if (diffHour > 0) return `${diffHour} hour${diffHour > 1 ? 's' : ''} ago`;
if (diffMin > 0) return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`;
return 'Just now';
}
// Escape HTML to prevent XSS
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Sidebar toggle (for mobile)
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const main = document.getElementById('mainContent');
if (window.innerWidth <= 1024) {
sidebar.classList.toggle('open');
} else {
sidebar.classList.toggle('collapsed');
main.classList.toggle('sidebar-collapsed');
localStorage.setItem('sidebarCollapsed', sidebar.classList.contains('collapsed'));
}
}
// Close sidebar when clicking outside (mobile)
document.addEventListener('click', (e) => {
const sidebar = document.getElementById('sidebar');
const menuBtn = document.querySelector('.yt-menu-btn');
if (window.innerWidth <= 1024 &&
sidebar &&
sidebar.classList.contains('open') &&
!sidebar.contains(e.target) &&
menuBtn && !menuBtn.contains(e.target)) {
sidebar.classList.remove('open');
}
});
// --- Theme Logic ---
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
// Update toggle if exists
const toggle = document.getElementById('themeToggle');
if (toggle) {
toggle.checked = savedTheme === 'dark';
}
}
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const newTheme = current === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
// --- Profile Logic ---
async function updateProfile(e) {
if (e) e.preventDefault();
const displayName = document.getElementById('displayName').value;
const btn = e.target.querySelector('button');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = ' Saving...';
try {
const response = await fetch('/api/update_profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: displayName })
});
const data = await response.json();
if (data.success) {
showToast('Profile updated successfully!', 'success');
// Update UI immediately
const avatarName = document.querySelector('.yt-avatar');
if (avatarName) avatarName.title = displayName;
} else {
showToast(data.message || 'Update failed', 'error');
}
} catch (err) {
showToast('Network error', 'error');
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
}
}