819acf04f8
Episode list: - Added 'Saison complète' header button to download all episodes at once - Added multi-select mode with checkboxes for batch episode download - Individual download buttons now show visual feedback (checkmark + reset) - Better grid/list toggle with selection state indicators Search results (anime + series): - Redesigned download dropdown with icons, descriptions, spinner on click - Smooth scale/opacity transitions on dropdown open/close - Consistent btn-success color for all download actions Series search JS: - Replaced basic <select> with scrollable episode list inline - Added 'Tout télécharger' button per series card - Replaced all alert() calls with toast notifications - Episode buttons show checkmark on successful download Anime details JS: - Added batch download button next to episode select - Fixed pre-existing lint error (escaped quote in translateSynopsis) - Standardized download icon to fa-arrow-down across all cards Recommendations + Tabs JS: - Unified download button color (btn-success) across all card types - Consistent icon (fa-arrow-down) for download actions Toast system: - Connected to existing Alpine.js toast infrastructure (show-toast events)
411 lines
19 KiB
JavaScript
411 lines
19 KiB
JavaScript
/**
|
|
* New tabs functionality
|
|
*/
|
|
|
|
// Render series recommendation card (same design as anime recommendations)
|
|
function renderSeriesRecommendationCard(series) {
|
|
let coverImage = series.cover_image || '';
|
|
|
|
// Convert relative poster.php URLs to absolute URLs
|
|
if (coverImage.startsWith('/poster.php?url=')) {
|
|
// Extract the actual image URL from the poster.php URL
|
|
const actualUrl = coverImage.replace('/poster.php?url=', '');
|
|
coverImage = actualUrl;
|
|
}
|
|
// If it's a relative path, make it absolute using FS7 base URL
|
|
else if (coverImage.startsWith('/')) {
|
|
coverImage = 'https://fs7.lol' + coverImage;
|
|
}
|
|
|
|
return `
|
|
<div class="card bg-base-200 border border-base-300 shadow-sm">
|
|
<div class="badge badge-primary badge-sm absolute top-2 right-2 z-10"><i class="fa-solid fa-music"></i> Série TV populaire</div>
|
|
|
|
<div class="card-body p-4">
|
|
<h4 class="card-title text-base">${escapeHtml(series.title)}</h4>
|
|
|
|
<div class="flex gap-3 mt-1">
|
|
${coverImage ? `<img src="${escapeHtml(coverImage)}" alt="" class="w-20 h-28 object-cover rounded-lg" onerror="this.style.display='none'">` : ''}
|
|
|
|
<div class="text-sm text-base-content/60">
|
|
<span class="badge badge-sm badge-ghost"><i class="fa-solid fa-tv"></i> Série TV</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-actions justify-end mt-3">
|
|
<button class="btn btn-secondary btn-sm" onclick="window.open('${escapeHtml(series.url)}', '_blank')">
|
|
<i class="fa-solid fa-link"></i> Voir sur FS7
|
|
</button>
|
|
<button class="btn btn-success btn-sm" onclick="loadSeriesEpisodes('${escapeHtml(series.url)}', '${escapeHtml(series.title)}')">
|
|
<i class="fa-solid fa-arrow-down"></i> Voir les épisodes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Load series episodes (redirects to series tab with search)
|
|
async function loadSeriesEpisodes(url, title) {
|
|
// Switch to series tab
|
|
switchTab('series');
|
|
|
|
// Fill search input with the series title
|
|
const searchInput = document.getElementById('seriesSearchInput');
|
|
if (searchInput) {
|
|
searchInput.value = title;
|
|
|
|
// Trigger search
|
|
setTimeout(() => {
|
|
if (typeof handleSeriesSearch === 'function') {
|
|
handleSeriesSearch();
|
|
}
|
|
}, 300);
|
|
}
|
|
}
|
|
|
|
// Render series release card (same design as anime releases)
|
|
function renderSeriesReleaseCard(series) {
|
|
let coverImage = series.cover_image || '';
|
|
|
|
// Convert relative poster.php URLs to absolute URLs
|
|
if (coverImage.startsWith('/poster.php?url=')) {
|
|
// Extract the actual image URL from the poster.php URL
|
|
const actualUrl = coverImage.replace('/poster.php?url=', '');
|
|
coverImage = actualUrl;
|
|
}
|
|
// If it's a relative path, make it absolute using FS7 base URL
|
|
else if (coverImage.startsWith('/')) {
|
|
coverImage = 'https://fs7.lol' + coverImage;
|
|
}
|
|
|
|
return `
|
|
<div class="card bg-base-200 border border-base-300 shadow-sm">
|
|
<div class="card-body p-4">
|
|
<h4 class="card-title text-base">${escapeHtml(series.title)}</h4>
|
|
|
|
<div class="flex gap-3 mt-1">
|
|
${coverImage ? `<img src="${escapeHtml(coverImage)}" alt="" class="w-20 h-28 object-cover rounded-lg" onerror="this.style.display='none'">` : ''}
|
|
|
|
<div class="text-sm text-base-content/60">
|
|
<span class="badge badge-sm badge-warning"><i class="fa-solid fa-tv"></i> Série TV • Nouveau</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-actions justify-end mt-3">
|
|
<button class="btn btn-secondary btn-sm" onclick="window.open('${escapeHtml(series.url)}', '_blank')">
|
|
<i class="fa-solid fa-link"></i> Voir sur FS7
|
|
</button>
|
|
<button class="btn btn-success btn-sm" onclick="loadSeriesEpisodes('${escapeHtml(series.url)}', '${escapeHtml(series.title)}')">
|
|
<i class="fa-solid fa-arrow-down"></i> Voir les épisodes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Load series recommendations for the Series tab
|
|
async function loadSeriesRecommendations() {
|
|
try {
|
|
const container = document.getElementById('seriesRecommendationsList');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '<div class="flex justify-center py-8"><span class="loading loading-spinner loading-md"></span><span class="ml-3 text-base-content/60">Chargement des recommandations séries...</span></div>';
|
|
|
|
// Search for popular series from all providers (including FS7)
|
|
const searchTerms = ['Breaking Bad', 'Game of Thrones', 'Stranger Things', 'The Walking Dead', 'The Last of Us', 'Wednesday', 'Squid Game', 'Peaky Blinders', 'House of the Dragon', 'The Witcher'];
|
|
const allSeries = [];
|
|
|
|
for (const term of searchTerms) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/series/search?q=${encodeURIComponent(term)}&lang=vf`);
|
|
const data = await response.json();
|
|
|
|
// Collect results from all providers, especially fs7
|
|
if (data.results) {
|
|
// Prioritize fs7 results
|
|
if (data.results['fs7'] && data.results['fs7'].length > 0) {
|
|
allSeries.push(...data.results['fs7'].slice(0, 2));
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Error searching for ${term}:`, error);
|
|
}
|
|
|
|
if (allSeries.length >= 12) break; // Limit to 12 series total
|
|
}
|
|
|
|
if (allSeries.length > 0) {
|
|
container.innerHTML = `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">${allSeries.map(series =>
|
|
renderSeriesRecommendationCard(series)
|
|
).join('')}</div>`;
|
|
} else {
|
|
container.innerHTML = '<div class="text-center py-16 text-base-content/50">Aucune recommandation trouvée</div>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading series recommendations:', error);
|
|
const container = document.getElementById('seriesRecommendationsList');
|
|
if (container) container.innerHTML = '<div class="text-center py-16 text-base-content/50">Erreur lors du chargement</div>';
|
|
}
|
|
}
|
|
|
|
// Load anime releases for the Anime tab
|
|
async function loadAnimeReleases() {
|
|
try {
|
|
const container = document.getElementById('animeReleasesList');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '<div class="flex justify-center py-8"><span class="loading loading-spinner loading-md"></span><span class="ml-3 text-base-content/60">Chargement des dernières sorties anime...</span></div>';
|
|
|
|
// Use the existing releases API
|
|
const response = await fetch(`${API_BASE}/releases/latest?limit=12`);
|
|
const data = await response.json();
|
|
|
|
if (data.releases && data.releases.length > 0) {
|
|
container.innerHTML = `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">${data.releases.map(anime =>
|
|
renderReleaseCard(anime)
|
|
).join('')}</div>`;
|
|
} else {
|
|
container.innerHTML = '<div class="text-center py-16 text-base-content/50">Aucune sortie trouvée</div>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading anime releases:', error);
|
|
const container = document.getElementById('animeReleasesList');
|
|
if (container) container.innerHTML = '<div class="text-center py-16 text-base-content/50">Erreur lors du chargement</div>';
|
|
}
|
|
}
|
|
|
|
// Load series releases for the Series tab
|
|
async function loadSeriesReleases() {
|
|
try {
|
|
const container = document.getElementById('seriesReleasesList');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '<div class="flex justify-center py-8"><span class="loading loading-spinner loading-md"></span><span class="ml-3 text-base-content/60">Chargement des dernières séries TV...</span></div>';
|
|
|
|
// Search for popular series from all providers (including FS7)
|
|
const searchTerms = ['Breaking Bad', 'Game of Thrones', 'Stranger Things', 'The Walking Dead', 'The Last of Us', 'Wednesday', 'Squid Game', 'Peaky Blinders'];
|
|
const allSeries = [];
|
|
|
|
for (const term of searchTerms) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/series/search?q=${encodeURIComponent(term)}&lang=vf`);
|
|
const data = await response.json();
|
|
|
|
// Collect results from all providers, especially fs7
|
|
if (data.results) {
|
|
// Prioritize fs7 results
|
|
if (data.results['fs7'] && data.results['fs7'].length > 0) {
|
|
allSeries.push(...data.results['fs7'].slice(0, 2));
|
|
}
|
|
// Add results from other providers if needed
|
|
for (const [provider, results] of Object.entries(data.results)) {
|
|
if (provider !== 'fs7' && results.length > 0 && allSeries.length < 12) {
|
|
allSeries.push(...results.slice(0, 1));
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Error searching for ${term}:`, error);
|
|
}
|
|
|
|
if (allSeries.length >= 12) break; // Limit to 12 series total
|
|
}
|
|
|
|
if (allSeries.length > 0) {
|
|
container.innerHTML = `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">${allSeries.map(series =>
|
|
renderSeriesReleaseCard(series)
|
|
).join('')}</div>`;
|
|
} else {
|
|
container.innerHTML = `
|
|
<div class="text-center py-16 text-base-content/50">
|
|
<p>Aucune série trouvée</p>
|
|
<p class="text-xs mt-2 opacity-70">
|
|
Utilisez l'onglet "Recherche" pour trouver des séries spécifiques
|
|
</p>
|
|
</div>`;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading series releases:', error);
|
|
const container = document.getElementById('seriesReleasesList');
|
|
if (container) {
|
|
container.innerHTML = `
|
|
<div class="text-center py-16 text-base-content/50">
|
|
<i class="fa-solid fa-xmark text-3xl mb-3 block"></i>
|
|
<p>Erreur lors du chargement des séries</p>
|
|
<p class="text-xs mt-2 text-error">${error.message}</p>
|
|
<button class="btn btn-secondary btn-sm mt-3" onclick="loadSeriesReleases()">
|
|
<i class="fa-solid fa-rotate"></i> Réessayer
|
|
</button>
|
|
</div>`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load providers grid for the Providers tab
|
|
async function loadProvidersGrid() {
|
|
try {
|
|
const container = document.getElementById('providersGrid');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '<div class="flex justify-center py-8"><span class="loading loading-spinner loading-md"></span><span class="ml-3 text-base-content/60">Chargement des fournisseurs...</span></div>';
|
|
|
|
const response = await fetch(`${API_BASE}/providers`);
|
|
const data = await response.json();
|
|
|
|
let html = '';
|
|
|
|
// Section Anime providers
|
|
html += '<div class="flex items-center gap-2 mt-5 mb-3"><h3 class="text-lg font-semibold"><i class="fa-solid fa-film"></i> Sites Anime</h3></div>';
|
|
html += '<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">';
|
|
|
|
const animeProviders = Object.entries(data.anime_providers || {});
|
|
if (animeProviders.length > 0) {
|
|
animeProviders.forEach(([id, provider]) => {
|
|
const domains = provider.domains || [];
|
|
html += `
|
|
<div class="card bg-base-200 border border-base-300 shadow-sm">
|
|
<div class="card-body p-4">
|
|
<h4 class="card-title text-base">${provider.icon} ${provider.name}</h4>
|
|
${domains.length > 0 ? `
|
|
<div class="text-sm mb-3">
|
|
<strong>Domaines:</strong><br>
|
|
<div class="flex flex-wrap gap-1 mt-1">
|
|
${domains.map(d => `<code class="badge badge-ghost badge-sm">${d}</code>`).join('')}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
<div class="card-actions justify-end">
|
|
${domains.length > 0 ? `
|
|
<button class="btn btn-primary btn-sm" onclick="window.open('https://${domains[0]}', '_blank')">
|
|
<i class="fa-solid fa-link"></i> Visiter le site
|
|
</button>
|
|
` : ''}
|
|
<button class="btn btn-secondary btn-sm" onclick="showProviderSearch('${id}')">
|
|
<i class="fa-solid fa-magnifying-glass"></i> Rechercher
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
} else {
|
|
html += '<div class="col-span-full text-center py-16 text-base-content/50">Aucun fournisseur anime disponible</div>';
|
|
}
|
|
|
|
html += '</div>';
|
|
|
|
// Section File hosts
|
|
html += '<div class="flex items-center gap-2 mt-10 mb-3"><h3 class="text-lg font-semibold"><i class="fa-solid fa-floppy-disk"></i> Hébergeurs de fichiers</h3></div>';
|
|
html += '<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">';
|
|
|
|
const fileHosts = Object.entries(data.file_hosts || {});
|
|
if (fileHosts.length > 0) {
|
|
fileHosts.forEach(([id, host]) => {
|
|
html += `
|
|
<div class="card bg-base-200 border border-base-300 shadow-sm">
|
|
<div class="card-body p-4">
|
|
<h4 class="card-title text-base">${host.icon} ${host.name}</h4>
|
|
<div class="card-actions justify-end">
|
|
<button class="btn btn-secondary btn-sm" onclick="showDownloadInfo()">
|
|
<i class="fa-solid fa-download"></i> Télécharger un fichier
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
} else {
|
|
html += '<div class="col-span-full text-center py-16 text-base-content/50">Aucun hébergeur disponible</div>';
|
|
}
|
|
|
|
html += '</div>';
|
|
container.innerHTML = html;
|
|
|
|
} catch (error) {
|
|
console.error('Error loading providers:', error);
|
|
const container = document.getElementById('providersGrid');
|
|
if (container) {
|
|
container.innerHTML = `
|
|
<div class="text-center py-16 text-base-content/50">
|
|
<i class="fa-solid fa-xmark text-3xl mb-3 block"></i>
|
|
<p>Erreur lors du chargement des fournisseurs</p>
|
|
<p class="text-xs mt-2 text-error">${error.message}</p>
|
|
<button class="btn btn-secondary btn-sm mt-3" onclick="loadProvidersGrid()">
|
|
<i class="fa-solid fa-rotate"></i> Réessayer
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show provider search (redirects to search tab)
|
|
function showProviderSearch(providerId) {
|
|
switchTab('search');
|
|
// Could pre-fill search with provider-specific content
|
|
}
|
|
|
|
// Show download info (explains how to download)
|
|
function showDownloadInfo() {
|
|
alert('Pour télécharger un fichier:\n\n1. Utilisez l\'onglet "Recherche"\n2. Entrez le nom de l\'anime/série\n3. Cliquez sur "Télécharger" sur un épisode\n\nOu bien:\n- Copiez directement un lien de téléchargement dans la barre d\'adresse de votre navigateur');
|
|
}
|
|
|
|
// Make additional functions available globally
|
|
window.showProviderSearch = showProviderSearch;
|
|
window.showDownloadInfo = showDownloadInfo;
|
|
|
|
// Initialize new tabs when they're first opened
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Wait for main.js to be loaded
|
|
setTimeout(() => {
|
|
// Override switchTab to load content when opening new tabs
|
|
const originalSwitchTab = window.switchTab;
|
|
if (originalSwitchTab) {
|
|
window.switchTab = function(tabName) {
|
|
// Call original switchTab first
|
|
originalSwitchTab(tabName);
|
|
|
|
// Load content for new tabs (after a small delay for DOM to update)
|
|
setTimeout(() => {
|
|
if (tabName === 'anime') {
|
|
if (!window.animeTabLoaded) {
|
|
loadAnimeReleases();
|
|
window.animeTabLoaded = true;
|
|
}
|
|
} else if (tabName === 'series') {
|
|
if (!window.seriesTabLoaded) {
|
|
loadSeriesRecommendations();
|
|
loadSeriesReleases();
|
|
window.seriesTabLoaded = true;
|
|
}
|
|
} else if (tabName === 'providers') {
|
|
if (!window.providersTabLoaded) {
|
|
loadProvidersGrid();
|
|
window.providersTabLoaded = true;
|
|
}
|
|
} else if (tabName === 'watchlist') {
|
|
if (!window.watchlistTabLoaded) {
|
|
if (typeof displayWatchlist === 'function') {
|
|
displayWatchlist();
|
|
}
|
|
window.watchlistTabLoaded = true;
|
|
if (typeof displayWatchlist === 'function') {
|
|
window.watchlistRefreshInterval = setInterval(() => { displayWatchlist(); }, 30000);
|
|
}
|
|
}
|
|
}
|
|
}, 100);
|
|
};
|
|
}
|
|
}, 500);
|
|
});
|
|
|
|
// Make functions available globally
|
|
window.loadSeriesEpisodes = loadSeriesEpisodes;
|
|
window.loadSeriesRecommendations = loadSeriesRecommendations;
|
|
window.loadAnimeReleases = loadAnimeReleases;
|
|
window.loadSeriesReleases = loadSeriesReleases;
|
|
window.loadProvidersGrid = loadProvidersGrid;
|