From c207a03f39473c22f2dbe8090765c6e54614b7c5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Dec 2025 22:33:13 +0000 Subject: [PATCH] Refactor movie playback and add clickability to stats Co-authored-by: mathieumoran1 --- public/css/app.css | 9 ++++++++ public/js/app_movies.js | 44 ++++++++++++++++++++++++++++---------- public/js/app_user-stat.js | 27 +++++++++++++++++++++++ routes/movies.js | 21 ------------------ routes/users.js | 1 + 5 files changed, 70 insertions(+), 32 deletions(-) diff --git a/public/css/app.css b/public/css/app.css index e6eac00..99228ee 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -236,6 +236,15 @@ footer { border-radius: 14px; } + .stats-movie-item--clickable { + cursor: pointer; + } + + .stats-movie-item--clickable:hover { + background: rgba(0, 0, 0, 0.02); + border-color: rgba(0, 0, 0, 0.14); + } + .stats-movie-poster { width: 56px; height: 84px; diff --git a/public/js/app_movies.js b/public/js/app_movies.js index 1a3d890..3322ae4 100644 --- a/public/js/app_movies.js +++ b/public/js/app_movies.js @@ -348,15 +348,26 @@ window.addEventListener('DOMContentLoaded', async function () { const isChecked = watchedMoviesInYear.some(watchedMovie => watchedMovie.imdb_id === movie.imdb_id); const watchedMovie = watchedMoviesInYear.find(wm => wm.imdb_id === movie.imdb_id); const watchedDate = watchedMovie ? new Date(watchedMovie.watchedDate).toLocaleString() : ''; - const hasVideoSrc = !!(movie && typeof movie.video_src === 'string' && movie.video_src.trim()); - const hasEmbedSrc = !!(movie && typeof movie.embed_src === 'string' && movie.embed_src.trim()); + const cleanUrl = (v) => { + const s = (typeof v === 'string' ? v.trim() : ''); + // Treat placeholders as "no link" so you never need to store '#' + if (!s || s === '#' || s.toLowerCase() === 'about:blank') return ''; + return s; + }; + const hasVideoSrc = !!cleanUrl(movie?.video_src); + const hasEmbedSrc = !!cleanUrl(movie?.embed_src); const hasNewPlayerSource = hasVideoSrc || hasEmbedSrc; - const hasLegacy = !!(movie && typeof movie.vod_link === 'string' && movie.vod_link.trim()); + const legacyUrl = cleanUrl(movie?.vod_link); + const hasLegacy = !!legacyUrl; - // If the movie only has legacy `vod_link`, skip the player entirely and open the original URL in a new tab. + // Navigation: + // - Prefer in-app player when we have a non-legacy source + // - If only legacy exists, open the legacy URL + // - If there is no source at all, render as non-clickable (no "#") const playerUrl = (movie && movie._id && hasNewPlayerSource) ? `/player.html?id=${encodeURIComponent(movie._id)}` - : (hasLegacy ? movie.vod_link : (movie && movie._id ? `/player.html?id=${encodeURIComponent(movie._id)}` : '#')); + : (hasLegacy ? legacyUrl : ''); + const isClickable = !!playerUrl; const movieDiv = document.createElement('div'); movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card'); @@ -364,15 +375,25 @@ window.addEventListener('DOMContentLoaded', async function () { movieDiv.innerHTML = `
${isChecked ? 'VISIONNÉ' : ''} - -
- ${movie.title} -
-
+ ${ + isClickable + ? ` +
+ ${movie.title} +
+
` + : `
+ ${movie.title} +
` + }
- ${movie.title} + ${ + isClickable + ? `${movie.title}` + : `${movie.title}` + }
⭐ ${movie.rating} @@ -381,6 +402,7 @@ window.addEventListener('DOMContentLoaded', async function () {
+ ${!isClickable ? '
Aucune source
' : ''}
${movie.category}

${movie.description}

${isChecked ? `
Regardé le: ${watchedDate}
` : ''} diff --git a/public/js/app_user-stat.js b/public/js/app_user-stat.js index c9e7bf0..ff35d84 100644 --- a/public/js/app_user-stat.js +++ b/public/js/app_user-stat.js @@ -435,6 +435,7 @@ window.addEventListener('DOMContentLoaded', async function () { moviesEmptyEl.classList.add('d-none'); sorted.forEach((movie) => { + const movieId = safeStr(movie?.movieId).trim(); const imdbId = safeStr(movie?.imdb_id).trim(); const title = safeStr(movie?.title).trim() || '(sans titre)'; const category = safeStr(movie?.category).trim(); @@ -445,6 +446,12 @@ window.addEventListener('DOMContentLoaded', async function () { const item = document.createElement('div'); item.className = 'stats-movie-item'; + if (movieId) { + item.classList.add('stats-movie-item--clickable'); + item.setAttribute('role', 'link'); + item.setAttribute('tabindex', '0'); + item.setAttribute('aria-label', `Ouvrir le lecteur: ${title}`); + } const poster = document.createElement('img'); poster.className = 'stats-movie-poster'; @@ -461,6 +468,26 @@ window.addEventListener('DOMContentLoaded', async function () { try { poster.removeAttribute('src'); } catch (_) {} }); + function openPlayerIfPossible(evt) { + if (!movieId) return; + // Don't hijack clicks inside controls (IMDb button, details summary, etc.) + const target = evt?.target; + if (target && typeof target.closest === 'function') { + if (target.closest('a, button, summary, details')) return; + } + window.location.href = `/player.html?id=${encodeURIComponent(movieId)}`; + } + + if (movieId) { + item.addEventListener('click', openPlayerIfPossible); + item.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + openPlayerIfPossible(e); + } + }); + } + const main = document.createElement('div'); main.className = 'flex-grow-1'; diff --git a/routes/movies.js b/routes/movies.js index 2221459..9ed1125 100644 --- a/routes/movies.js +++ b/routes/movies.js @@ -361,16 +361,6 @@ router.post("/add", async (req, res) => { if (normalizedMode === null) { return res.status(400).json({ message: "player_mode invalide (auto|video|embed)" }); } - if ( - !hasAnySourceIncludingFile({ - vod_link: normalizedVod || "", - video_src: normalizedVideo || "", - embed_src: normalizedEmbed || "", - video_file: normalizedFile || "", - }) - ) { - return res.status(400).json({ message: "Ajoute au moins une source (VOD / video_src / embed_src)." }); - } // Fetch movie details from OMDb API let movieDetails; @@ -584,17 +574,6 @@ router.put("/:id", async (req, res) => { if (poster !== undefined) movie.poster = poster; } - if ( - !hasAnySourceIncludingFile({ - vod_link: movie.vod_link || "", - video_src: movie.video_src || "", - embed_src: movie.embed_src || "", - video_file: movie.video_file || "", - }) - ) { - return res.status(400).json({ message: "Ajoute au moins une source (VOD / video_src / embed_src / video_file)." }); - } - await movie.save(); cacheClear(); return res.status(200).json(movie); diff --git a/routes/users.js b/routes/users.js index 012b953..4e989a8 100644 --- a/routes/users.js +++ b/routes/users.js @@ -151,6 +151,7 @@ router.get("/stats", verifyToken, async (req, res) => { const movie = movieByImdbId.get(imdbId); if (!movie) return null; return { + movieId: movie?._id ? String(movie._id) : null, imdb_id: movie.imdb_id, title: movie.title || "", poster: movie.poster || "",