window.App = window.App || {}; App.favorites = App.favorites || {}; (function() { const { FAVORITES_KEY, FAVORITES_VISIBILITY_KEY } = App.constants; // Favorites storage helpers. App.favorites.getAll = function() { try { const raw = localStorage.getItem(FAVORITES_KEY); const parsed = raw ? JSON.parse(raw) : []; return Array.isArray(parsed) ? parsed : []; } catch (err) { return []; } }; App.favorites.setAll = function(items) { localStorage.setItem(FAVORITES_KEY, JSON.stringify(items)); }; App.favorites.getKey = function(video) { if (!video) return null; const meta = video.meta || video; return video.key || meta.key || video.id || meta.id || video.url || meta.url || null; }; App.favorites.normalize = function(video) { const key = App.favorites.getKey(video); if (!key) return null; const meta = video && video.meta ? video.meta : video; return { key, id: video.id || null, url: video.url || '', title: video.title || '', thumb: video.thumb || '', channel: video.channel || (meta && meta.channel) || '', uploader: video.uploader || (meta && meta.uploader) || '', duration: video.duration || (meta && meta.duration) || 0, meta: meta }; }; App.favorites.getSet = function() { return new Set(App.favorites.getAll().map((item) => item.key)); }; App.favorites.isVisible = function() { return localStorage.getItem(FAVORITES_VISIBILITY_KEY) !== 'false'; }; App.favorites.setVisible = function(isVisible) { localStorage.setItem(FAVORITES_VISIBILITY_KEY, isVisible ? 'true' : 'false'); }; // UI helpers for rendering and syncing heart states. App.favorites.setButtonState = function(button, isFavorite) { button.classList.toggle('is-favorite', isFavorite); button.textContent = isFavorite ? '♥' : '♡'; button.setAttribute('aria-pressed', isFavorite ? 'true' : 'false'); button.setAttribute('aria-label', isFavorite ? 'Remove from favorites' : 'Add to favorites'); }; App.favorites.syncButtons = function() { const favoritesSet = App.favorites.getSet(); document.querySelectorAll('.favorite-btn[data-fav-key]').forEach((button) => { const key = button.dataset.favKey; if (!key) return; App.favorites.setButtonState(button, favoritesSet.has(key)); }); }; App.favorites.toggle = function(video) { const key = App.favorites.getKey(video); if (!key) return; const favorites = App.favorites.getAll(); const existingIndex = favorites.findIndex((item) => item.key === key); if (existingIndex >= 0) { favorites.splice(existingIndex, 1); } else { const entry = App.favorites.normalize(video); if (entry) favorites.unshift(entry); } App.favorites.setAll(favorites); App.favorites.renderBar(); App.favorites.syncButtons(); }; App.favorites.renderBar = function() { const bar = document.getElementById('favorites-bar'); const list = document.getElementById('favorites-list'); const empty = document.getElementById('favorites-empty'); if (!bar || !list) return; const favorites = App.favorites.getAll(); const visible = App.favorites.isVisible(); bar.style.display = visible ? 'block' : 'none'; list.innerHTML = ""; favorites.forEach((item) => { const card = document.createElement('div'); card.className = 'favorite-card'; card.dataset.favKey = item.key; const uploaderText = item.uploader || ''; card.innerHTML = ` ${item.title}

${item.title}

${uploaderText ? `

` : ''}
`; const thumb = card.querySelector('img'); if (App.videos && typeof App.videos.attachNoReferrerRetry === 'function') { App.videos.attachNoReferrerRetry(thumb); } card.onclick = () => { if (card.classList.contains('is-loading')) return; card.classList.add('is-loading'); App.player.open(item.meta || item, { originEl: card }); }; const favoriteBtn = card.querySelector('.favorite-btn'); if (favoriteBtn) { favoriteBtn.onclick = (event) => { event.stopPropagation(); App.favorites.toggle(item); }; } const menuBtn = card.querySelector('.video-menu-btn'); const menu = card.querySelector('.video-menu'); const showInfoBtn = card.querySelector('.video-menu-item[data-action="info"]'); const downloadBtn = card.querySelector('.video-menu-item[data-action="download"]'); if (menuBtn && menu) { menuBtn.onclick = (event) => { event.stopPropagation(); App.videos.toggleMenu(menu, menuBtn); }; } if (showInfoBtn) { showInfoBtn.onclick = (event) => { event.stopPropagation(); App.ui.showInfo(item.meta || item); App.videos.closeAllMenus(); }; } if (downloadBtn) { downloadBtn.onclick = (event) => { event.stopPropagation(); App.videos.downloadVideo(item.meta || item); App.videos.closeAllMenus(); }; } const uploaderBtn = card.querySelector('.uploader-link'); if (uploaderBtn) { uploaderBtn.onclick = (event) => { event.stopPropagation(); const uploader = uploaderBtn.dataset.uploader || uploaderBtn.textContent || ''; App.videos.handleSearch(uploader); }; } list.appendChild(card); }); if (empty) { empty.style.display = favorites.length > 0 ? 'none' : 'block'; } }; })();