Add shareable search links and watched movie indicators
Share: "Copy Link" button generates a URL with ?s={history_id} that
loads the saved search results without auth. Recipients see the same
movie picks.
Watched: When viewing history results or shared searches, cards for
movies the logged-in user has since watched are dimmed with a green
"Watched" badge. Uses a new POST /api/library/watch-check endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+64
-1
@@ -217,6 +217,7 @@ function renderResults(data, append = false) {
|
||||
data.recommendations.forEach(movie => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'movie-card bg-dark-200 rounded-xl overflow-hidden border border-gray-800';
|
||||
card.dataset.jellyfinId = movie.jellyfin_id;
|
||||
card.innerHTML = `
|
||||
<div class="aspect-[2/3] bg-dark-300 relative">
|
||||
<img src="${movie.poster_url}" alt="${movie.title}"
|
||||
@@ -315,11 +316,13 @@ async function loadHistory() {
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function showHistoryResults(entry) {
|
||||
async function showHistoryResults(entry) {
|
||||
document.getElementById('mood-input').value = entry.mood;
|
||||
const data = { recommendations: entry.results, meta: entry.meta };
|
||||
renderResults(data);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
// Check watch state and dim watched movies
|
||||
await markWatchedCards(entry.results.map(r => r.jellyfin_id));
|
||||
}
|
||||
|
||||
function toggleHistoryVisibility(show) {
|
||||
@@ -334,6 +337,51 @@ function toggleHistoryVisibility(show) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Watch Check ---
|
||||
|
||||
async function markWatchedCards(jellyfinIds) {
|
||||
if (!currentUser || !jellyfinIds.length) return;
|
||||
try {
|
||||
const res = await fetch(`${API}/api/library/watch-check`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ jellyfin_ids: jellyfinIds })
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const { watched } = await res.json();
|
||||
if (!watched.length) return;
|
||||
const watchedSet = new Set(watched);
|
||||
document.querySelectorAll('.movie-card').forEach(card => {
|
||||
if (watchedSet.has(card.dataset.jellyfinId)) {
|
||||
card.classList.add('watched');
|
||||
}
|
||||
});
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// --- Shared Search ---
|
||||
|
||||
async function loadSharedSearch() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const sharedId = params.get('s');
|
||||
if (!sharedId) return false;
|
||||
try {
|
||||
const res = await fetch(`${API}/api/history/shared/${sharedId}`);
|
||||
if (!res.ok) return false;
|
||||
const entry = await res.json();
|
||||
document.getElementById('mood-input').value = entry.mood;
|
||||
const data = { recommendations: entry.results, meta: entry.meta };
|
||||
renderResults(data);
|
||||
// If logged in, check watch state
|
||||
if (currentUser) {
|
||||
await markWatchedCards(entry.results.map(r => r.jellyfin_id));
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Library Stats ---
|
||||
|
||||
async function loadStats() {
|
||||
@@ -418,6 +466,20 @@ document.getElementById('reroll-btn').addEventListener('click', () => {
|
||||
findMovies(shownMovieIds);
|
||||
});
|
||||
|
||||
// Share / Copy Link
|
||||
document.getElementById('share-btn').addEventListener('click', () => {
|
||||
if (!currentHistoryId) return;
|
||||
const url = `${window.location.origin}${window.location.pathname}?s=${currentHistoryId}`;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
const btn = document.getElementById('share-btn');
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => { btn.textContent = 'Copy Link'; }, 2000);
|
||||
}).catch(() => {
|
||||
// Fallback for insecure contexts
|
||||
prompt('Copy this link:', `${window.location.origin}${window.location.pathname}?s=${currentHistoryId}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Reset
|
||||
document.getElementById('reset-btn').addEventListener('click', () => {
|
||||
// Clear mood input and state
|
||||
@@ -445,3 +507,4 @@ document.getElementById('reset-btn').addEventListener('click', () => {
|
||||
|
||||
// --- Init ---
|
||||
checkAuth();
|
||||
loadSharedSearch();
|
||||
|
||||
Reference in New Issue
Block a user