fix: resolve all DaisyUI audit issues
- settings.js: replace broken CSS vars with getThemeColor() helper - base.html: add bg-primary text-primary-content active state to drawer - All templates: btn-small -> btn-sm (DaisyUI standard) - Delete orphan templates/components/header.html - auth-utils.js: fix .show class -> use hidden (Tailwind) - login.html: remove redundant auth-* classes, keep DaisyUI only - auth-ui.js: update form selector for cleanup - watchlist.html: fix nav active class styling - 4 JS files (series-search, tabs, recommendations, anime-details): - Replace all old CSS classes with DaisyUI/Tailwind - Remove hardcoded colors, use theme-aware classes - loading-spinner -> DaisyUI loading component - no-results/search-results -> Tailwind utility layout - All badges -> DaisyUI badge variants
This commit is contained in:
+95
-86
@@ -7,7 +7,7 @@ async function searchAnimeDetails(query, malId = null) {
|
||||
if (!resultsContainer) return;
|
||||
|
||||
try {
|
||||
resultsContainer.innerHTML = '<div class="loading-spinner">Recherche en cours...</div>';
|
||||
resultsContainer.innerHTML = '<div class="flex justify-center py-8"><span class="loading loading-spinner loading-md"></span><span class="ml-3 text-base-content/60">Recherche en cours...</span></div>';
|
||||
|
||||
// If we have a MAL ID, fetch directly by ID, otherwise search by query
|
||||
let malUrl;
|
||||
@@ -81,10 +81,10 @@ async function searchAnimeDetails(query, malId = null) {
|
||||
// Only add header and wrapper if we have results
|
||||
if (hasResults) {
|
||||
streamingParts.unshift(
|
||||
`<div class="streaming-results-header">
|
||||
<h3><i class="fa-solid fa-film"></i> Résultats de streaming</h3>
|
||||
`<div class="flex items-center gap-2 mb-4 mt-5">
|
||||
<h3 class="text-lg font-semibold"><i class="fa-solid fa-film"></i> Résultats de streaming</h3>
|
||||
</div>
|
||||
<div class="search-results" style="margin-top: 20px;">`
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">`
|
||||
);
|
||||
streamingParts.push('</div>');
|
||||
streamingHtml = streamingParts.join('');
|
||||
@@ -109,9 +109,10 @@ async function searchAnimeDetails(query, malId = null) {
|
||||
// MAL found nothing but we have streaming results
|
||||
if (streamingHtml) {
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="no-results" style="margin-bottom: 20px;">
|
||||
<p><i class="fa-solid fa-circle-info"></i> Aucune fiche trouvée sur MyAnimeList pour "${escapeHtml(query)}"</p>
|
||||
<p style="font-size: 12px; margin-top: 10px; color: #888;">
|
||||
<div class="text-center py-12 text-base-content/50 mb-5">
|
||||
<i class="fa-solid fa-circle-info text-3xl mb-3 block"></i>
|
||||
<p>Aucune fiche trouvée sur MyAnimeList pour "${escapeHtml(query)}"</p>
|
||||
<p class="text-xs mt-2 text-base-content/40">
|
||||
Essayez le nom en anglais ou japonais (ex: "Frieren: Beyond Journey's End")
|
||||
</p>
|
||||
</div>
|
||||
@@ -124,9 +125,10 @@ async function searchAnimeDetails(query, malId = null) {
|
||||
}
|
||||
} else {
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="no-results">
|
||||
<p><i class="fa-solid fa-xmark"></i> Aucun résultat trouvé pour "${escapeHtml(query)}"</p>
|
||||
<p style="font-size: 12px; margin-top: 10px; color: #888;">
|
||||
<div class="text-center py-16 text-base-content/50">
|
||||
<i class="fa-solid fa-xmark text-3xl mb-3 block"></i>
|
||||
<p>Aucun résultat trouvé pour "${escapeHtml(query)}"</p>
|
||||
<p class="text-xs mt-2 text-base-content/40">
|
||||
Essayez le nom en anglais ou japonais (ex: "Frieren: Beyond Journey's End", "One Piece")
|
||||
</p>
|
||||
</div>
|
||||
@@ -137,9 +139,10 @@ async function searchAnimeDetails(query, malId = null) {
|
||||
} catch (error) {
|
||||
console.error('Error searching anime details:', error);
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="no-results">
|
||||
<p><i class="fa-solid fa-xmark"></i> Erreur lors de la recherche.</p>
|
||||
<p style="font-size: 12px; margin-top: 10px; color: #ff6b6b;">${error.message}</p>
|
||||
<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 de la recherche.</p>
|
||||
<p class="text-xs mt-2 text-error">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -176,10 +179,10 @@ async function getProviderSearchResults(query) {
|
||||
// Only add header and wrapper if we have results
|
||||
if (hasResults) {
|
||||
htmlParts.unshift(
|
||||
`<div class="streaming-results-header">
|
||||
<h3><i class="fa-solid fa-film"></i> Résultats de streaming</h3>
|
||||
`<div class="flex items-center gap-2 mb-4 mt-5">
|
||||
<h3 class="text-lg font-semibold"><i class="fa-solid fa-film"></i> Résultats de streaming</h3>
|
||||
</div>
|
||||
<div class="search-results" style="margin-top: 20px;">`
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">`
|
||||
);
|
||||
htmlParts.push('</div>');
|
||||
}
|
||||
@@ -237,24 +240,24 @@ function renderAnimeDetails(anime) {
|
||||
});
|
||||
|
||||
return `
|
||||
<div class="anime-details-card">
|
||||
<div class="card bg-base-200 border border-base-300 shadow-lg">
|
||||
<!-- Header with poster and basic info -->
|
||||
<div class="anime-details-header">
|
||||
${imageUrl ? `<img src="${escapeHtml(imageUrl)}" alt="" class="anime-details-poster">` : ''}
|
||||
<div class="flex flex-col md:flex-row gap-4 p-4">
|
||||
${imageUrl ? `<img src="${escapeHtml(imageUrl)}" alt="" class="w-40 h-56 object-cover rounded-lg shrink-0">` : ''}
|
||||
|
||||
<div class="anime-details-info">
|
||||
<h2 class="anime-details-title">${escapeHtml(anime.title)}</h2>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h2 class="text-xl font-bold">${escapeHtml(anime.title)}</h2>
|
||||
${anime.title_english && anime.title_english !== anime.title ? `
|
||||
<p class="anime-details-subtitle">${escapeHtml(anime.title_english)}</p>
|
||||
<p class="text-sm text-base-content/60">${escapeHtml(anime.title_english)}</p>
|
||||
` : ''}
|
||||
|
||||
<div class="anime-details-meta">
|
||||
${score > 0 ? `<div class="anime-details-rating"><i class="fa-solid fa-star"></i> ${score.toFixed(2)}</div>` : ''}
|
||||
${rank > 0 ? `<div class="anime-details-rank">#${rank}</div>` : ''}
|
||||
${popularity > 0 ? `<div class="anime-details-popularity">Popularity #${popularity}</div>` : ''}
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
${score > 0 ? `<span class="badge badge-warning badge-sm"><i class="fa-solid fa-star"></i> ${score.toFixed(2)}</span>` : ''}
|
||||
${rank > 0 ? `<span class="badge badge-outline badge-sm">#${rank}</span>` : ''}
|
||||
${popularity > 0 ? `<span class="badge badge-ghost badge-sm">Popularité #${popularity}</span>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="anime-details-stats">
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 mt-3 text-sm text-base-content/70">
|
||||
${anime.episodes ? `<span><i class="fa-solid fa-tv"></i> ${anime.episodes} épisodes</span>` : ''}
|
||||
${anime.status ? `<span><i class="fa-solid fa-tower-broadcast"></i> ${translateStatus(anime.status)}</span>` : ''}
|
||||
${anime.duration ? `<span><i class="fa-solid fa-clock"></i> ${escapeHtml(anime.duration)}</span>` : ''}
|
||||
@@ -262,16 +265,16 @@ function renderAnimeDetails(anime) {
|
||||
</div>
|
||||
|
||||
${studios.length > 0 ? `
|
||||
<div class="anime-details-studios">
|
||||
<div class="text-sm mt-2 text-base-content/60">
|
||||
Studio: ${studios.map(s => escapeHtml(s)).join(', ')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="anime-details-actions">
|
||||
<a href="${escapeHtml(anime.url)}" target="_blank" class="btn btn-secondary btn-small">
|
||||
<div class="flex flex-wrap gap-2 mt-3">
|
||||
<a href="${escapeHtml(anime.url)}" target="_blank" class="btn btn-secondary btn-sm">
|
||||
<i class="fa-solid fa-link"></i> Voir sur MAL
|
||||
</a>
|
||||
<button onclick="searchAnimeOnProviders('${escapeHtml(anime.title)}')" class="btn btn-primary btn-small">
|
||||
<button onclick="searchAnimeOnProviders('${escapeHtml(anime.title)}')" class="btn btn-primary btn-sm">
|
||||
<i class="fa-solid fa-download"></i> Télécharger
|
||||
</button>
|
||||
</div>
|
||||
@@ -280,39 +283,40 @@ function renderAnimeDetails(anime) {
|
||||
|
||||
<!-- Genres and themes -->
|
||||
${(genres.length > 0 || themes.length > 0) ? `
|
||||
<div class="anime-details-tags">
|
||||
${genres.map(g => `<span class="anime-details-tag genre">${escapeHtml(g)}</span>`).join('')}
|
||||
${themes.map(t => `<span class="anime-details-tag theme">${escapeHtml(t)}</span>`).join('')}
|
||||
<div class="px-4 pb-3 flex flex-wrap gap-1">
|
||||
${genres.map(g => `<span class="badge badge-primary badge-outline badge-sm">${escapeHtml(g)}</span>`).join('')}
|
||||
${themes.map(t => `<span class="badge badge-accent badge-outline badge-sm">${escapeHtml(t)}</span>`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Synopsis with translation button -->
|
||||
${synopsis ? `
|
||||
<div class="anime-details-section">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||||
<h3 style="margin: 0;"><i class="fa-solid fa-book"></i> Synopsis</h3>
|
||||
<button onclick="translateSynopsis('${synopsisId}', this)" class="btn btn-secondary btn-small" style="font-size: 12px;">
|
||||
<div class="px-4 pb-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="font-semibold"><i class="fa-solid fa-book"></i> Synopsis</h3>
|
||||
<button onclick="translateSynopsis('${synopsisId}', this)" class="btn btn-secondary btn-sm btn-xs">
|
||||
<i class="fa-solid fa-globe"></i> Traduire en français
|
||||
</button>
|
||||
</div>
|
||||
<p id="${synopsisId}" class="anime-details-synopsis">${escapeHtml(synopsis)}</p>
|
||||
<p id="${synopsisId}" class="text-sm text-base-content/80 leading-relaxed">${escapeHtml(synopsis)}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Seasons (Sequel/Prequel) -->
|
||||
${seasons.length > 0 ? `
|
||||
<div class="anime-details-section">
|
||||
<h3><i class="fa-solid fa-tv"></i> Saisons</h3>
|
||||
<div class="anime-related-list">
|
||||
<div class="px-4 pb-4">
|
||||
<h3 class="font-semibold mb-2"><i class="fa-solid fa-tv"></i> Saisons</h3>
|
||||
<div class="space-y-3">
|
||||
${seasons.map(season => `
|
||||
<div class="anime-related-group">
|
||||
<div class="anime-related-type">${translateRelationType(season.type)}</div>
|
||||
<div class="anime-related-items">
|
||||
<div>
|
||||
<div class="badge badge-info badge-sm mb-1">${translateRelationType(season.type)}</div>
|
||||
<div class="space-y-1">
|
||||
${season.entries.map(entry => `
|
||||
<div class="anime-related-item" onclick="searchAnimeDetails('${escapeHtml(entry.title)}', ${entry.mal_id})" style="cursor: pointer;">
|
||||
${entry.type ? `<span style="color: #FFBF69; font-size: 11px; margin-right: 8px;">${escapeHtml(entry.type)}</span>` : ''}
|
||||
${escapeHtml(entry.title)}
|
||||
${entry.url ? `<a href="${escapeHtml(entry.url)}" target="_blank" style="margin-left: auto; color: #888; font-size: 18px; text-decoration: none;" title="Voir sur MyAnimeList">↗</a>` : ''}
|
||||
<div class="flex items-center gap-2 p-2 rounded-lg hover:bg-base-300/50 cursor-pointer"
|
||||
onclick="searchAnimeDetails('${escapeHtml(entry.title)}', ${entry.mal_id})">
|
||||
${entry.type ? `<span class="badge badge-warning badge-sm shrink-0">${escapeHtml(entry.type)}</span>` : ''}
|
||||
<span class="text-sm">${escapeHtml(entry.title)}</span>
|
||||
${entry.url ? `<a href="${escapeHtml(entry.url)}" target="_blank" class="ml-auto text-base-content/30 hover:text-base-content text-lg" title="Voir sur MyAnimeList" onclick="event.stopPropagation()">↗</a>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -332,7 +336,7 @@ async function loadStreamingResults(query) {
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
container.innerHTML = '<div class="loading-spinner">Recherche des sources de streaming...</div>';
|
||||
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">Recherche des sources de streaming...</span></div>';
|
||||
|
||||
// Load providers info
|
||||
const providersData = await getProvidersInfo();
|
||||
@@ -357,8 +361,9 @@ async function loadStreamingResults(query) {
|
||||
|
||||
if (successfulResults.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="no-results">
|
||||
<p><i class="fa-solid fa-triangle-exclamation"></i> Aucun résultat de streaming trouvé pour "${escapeHtml(query)}"</p>
|
||||
<div class="text-center py-16 text-base-content/50">
|
||||
<i class="fa-solid fa-triangle-exclamation text-3xl mb-3 block"></i>
|
||||
<p>Aucun résultat de streaming trouvé pour "${escapeHtml(query)}"</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -366,10 +371,10 @@ async function loadStreamingResults(query) {
|
||||
|
||||
// Display results
|
||||
container.innerHTML = `
|
||||
<div class="streaming-results-header">
|
||||
<h3><i class="fa-solid fa-film"></i> Disponible sur</h3>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<h3 class="text-lg font-semibold"><i class="fa-solid fa-film"></i> Disponible sur</h3>
|
||||
</div>
|
||||
<div class="streaming-results-grid">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
${successfulResults.map(result => renderStreamingResult(result, query)).join('')}
|
||||
</div>
|
||||
`;
|
||||
@@ -377,8 +382,9 @@ async function loadStreamingResults(query) {
|
||||
} catch (error) {
|
||||
console.error('Error loading streaming results:', error);
|
||||
container.innerHTML = `
|
||||
<div class="no-results">
|
||||
<p><i class="fa-solid fa-xmark"></i> Erreur lors de la recherche des sources de streaming.</p>
|
||||
<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 de la recherche des sources de streaming.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -389,30 +395,34 @@ function renderStreamingResult(result, query) {
|
||||
const { provider, name, icon, episodes } = result;
|
||||
|
||||
return `
|
||||
<div class="streaming-result-card">
|
||||
<div class="streaming-result-header">
|
||||
<span class="streaming-result-icon">${icon}</span>
|
||||
<span class="streaming-result-name">${escapeHtml(name)}</span>
|
||||
<span class="streaming-result-count">${episodes.length} épisodes</span>
|
||||
<div class="card bg-base-200 border border-base-300 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">${icon}</span>
|
||||
<span class="font-semibold text-sm">${escapeHtml(name)}</span>
|
||||
</div>
|
||||
<span class="badge badge-ghost badge-sm">${episodes.length} épisodes</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<select class="select select-bordered select-sm w-full streaming-episode-select" data-provider="${provider}" data-query="${escapeHtml(query)}">
|
||||
<option value="">Sélectionner un épisode</option>
|
||||
${episodes.slice(0, 20).map(ep => `
|
||||
<option value="${escapeHtml(ep.url)}">Épisode ${ep.episode}</option>
|
||||
`).join('')}
|
||||
${episodes.length > 20 ? `<option disabled>... et ${episodes.length - 20} autres</option>` : ''}
|
||||
</select>
|
||||
|
||||
<button class="btn btn-primary btn-sm w-full streaming-download-btn" onclick="downloadSelectedEpisode(this)">
|
||||
<i class="fa-solid fa-download"></i> Télécharger
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a href="#" class="link link-hover text-xs mt-2" onclick="searchAnimeOnProviders('${escapeHtml(query)}'); return false;">
|
||||
Voir tous les épisodes sur ${escapeHtml(name)} →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="streaming-result-episodes">
|
||||
<select class="streaming-episode-select" data-provider="${provider}" data-query="${escapeHtml(query)}">
|
||||
<option value="">Sélectionner un épisode</option>
|
||||
${episodes.slice(0, 20).map(ep => `
|
||||
<option value="${escapeHtml(ep.url)}">Épisode ${ep.episode}</option>
|
||||
`).join('')}
|
||||
${episodes.length > 20 ? `<option disabled>... et ${episodes.length - 20} autres</option>` : ''}
|
||||
</select>
|
||||
|
||||
<button class="btn btn-primary btn-small streaming-download-btn" onclick="downloadSelectedEpisode(this)">
|
||||
<i class="fa-solid fa-download"></i> Télécharger
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a href="#" class="streaming-result-link" onclick="searchAnimeOnProviders('${escapeHtml(query)}'); return false;">
|
||||
Voir tous les épisodes sur ${escapeHtml(name)} →
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -509,7 +519,7 @@ async function translateSynopsis(synopsisId, button) {
|
||||
|
||||
synopsisElement.textContent = data.translatedText;
|
||||
synopsisElement.dataset.translated = 'true';
|
||||
button.innerHTML = '<i class="fa-solid fa-rotate"></i> Voir l\'original';
|
||||
button.innerHTML = '<i class="fa-solid fa-rotate"></i> Voir l\\'original';
|
||||
} else {
|
||||
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
||||
console.error('Translation API error:', errorData);
|
||||
@@ -519,12 +529,12 @@ async function translateSynopsis(synopsisId, button) {
|
||||
console.error('Translation error:', error);
|
||||
synopsisElement.style.opacity = '1';
|
||||
|
||||
// Show user-friendly error
|
||||
// Show user-friendly error using DaisyUI alert styling
|
||||
const errorMessage = document.createElement('div');
|
||||
errorMessage.style.cssText = 'margin-top: 10px; padding: 10px; background: rgba(255, 107, 107, 0.2); border-radius: 8px; font-size: 12px; color: #ff6b6b;';
|
||||
errorMessage.className = 'alert alert-error alert-sm mt-2 text-xs translation-error';
|
||||
errorMessage.innerHTML = `
|
||||
<i class="fa-solid fa-triangle-exclamation"></i> Service de traduction temporairement indisponible.<br>
|
||||
<small>Essayez à nouveau dans quelques instants.</small>
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
<span>Service de traduction temporairement indisponible. Essayez à nouveau dans quelques instants.</span>
|
||||
`;
|
||||
|
||||
// Remove existing error message if any
|
||||
@@ -533,7 +543,6 @@ async function translateSynopsis(synopsisId, button) {
|
||||
existingError.remove();
|
||||
}
|
||||
|
||||
errorMessage.className = 'translation-error';
|
||||
synopsisElement.parentElement.appendChild(errorMessage);
|
||||
|
||||
// Auto-remove error after 5 seconds
|
||||
|
||||
Reference in New Issue
Block a user