171 lines
6.9 KiB
JavaScript
171 lines
6.9 KiB
JavaScript
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 || meta.channel)) || '',
|
|
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 || item.channel || '';
|
|
card.innerHTML = `
|
|
<button class="favorite-btn is-favorite" type="button" aria-pressed="true" aria-label="Remove from favorites" data-fav-key="${item.key}">♥</button>
|
|
<button class="video-menu-btn" type="button" aria-haspopup="true" aria-expanded="false" aria-label="More options">⋯</button>
|
|
<div class="video-menu" role="menu">
|
|
<button class="video-menu-item" type="button" data-action="info" role="menuitem">Show info</button>
|
|
<button class="video-menu-item" type="button" data-action="download" role="menuitem">Download</button>
|
|
</div>
|
|
<img src="${item.thumb}" alt="${item.title}">
|
|
<div class="favorite-info">
|
|
<h4>${item.title}</h4>
|
|
${uploaderText ? `<p><button class="uploader-link" type="button" data-uploader="${uploaderText}">${uploaderText}</button></p>` : ''}
|
|
</div>
|
|
`;
|
|
const thumb = card.querySelector('img');
|
|
if (App.videos && typeof App.videos.attachNoReferrerRetry === 'function') {
|
|
App.videos.attachNoReferrerRetry(thumb);
|
|
}
|
|
card.onclick = () => App.player.open(item.meta || item);
|
|
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';
|
|
}
|
|
};
|
|
})();
|