fix: centralize watchlist dropdown js and fix page load timing

This commit is contained in:
2026-05-13 19:05:10 +02:00
parent 950e143faf
commit b3c906a16e
4 changed files with 71 additions and 134 deletions

View File

@@ -188,6 +188,7 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
// 3. Get start time from progress // 3. Get start time from progress
startTime := 0.0 startTime := 0.0
var watchlistStatus string var watchlistStatus string
var watchlistIDs []int64
if userID != "" { if userID != "" {
entry, err := s.repo.GetWatchListEntry(ctx, db.GetWatchListEntryParams{ entry, err := s.repo.GetWatchListEntry(ctx, db.GetWatchListEntryParams{
UserID: userID, UserID: userID,
@@ -195,6 +196,7 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
}) })
if err == nil { if err == nil {
watchlistStatus = entry.Status watchlistStatus = entry.Status
watchlistIDs = []int64{entry.AnimeID}
if entry.CurrentEpisode.Valid && strconv.FormatInt(entry.CurrentEpisode.Int64, 10) == episode { if entry.CurrentEpisode.Valid && strconv.FormatInt(entry.CurrentEpisode.Int64, 10) == episode {
startTime = entry.CurrentTimeSeconds startTime = entry.CurrentTimeSeconds
} }
@@ -318,6 +320,7 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
"Episodes": domainEpisodes, "Episodes": domainEpisodes,
"CurrentEpID": episode, "CurrentEpID": episode,
"WatchlistStatus": watchlistStatus, "WatchlistStatus": watchlistStatus,
"WatchlistIDs": watchlistIDs,
"Seasons": seasons, "Seasons": seasons,
}, nil }, nil
} }

View File

@@ -109,7 +109,20 @@
const watchlistIds = new Set() const watchlistIds = new Set()
function initWatchlist(ids) { function initWatchlist(ids) {
ids.forEach(id => watchlistIds.add(id)) ids.forEach(id => watchlistIds.add(id));
const sync = () => ids.forEach(id => syncRemoveButtonVisibility(id));
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', sync);
} else {
sync();
}
}
function syncRemoveButtonVisibility(id) {
const container = document.getElementById('remove-watchlist-container-' + id);
if (container) {
container.classList.toggle('hidden', !watchlistIds.has(id));
}
} }
function toggleWatchlist(id, btn) { function toggleWatchlist(id, btn) {
@@ -169,10 +182,7 @@ if (window.showToast) showToast({ message: 'Something went wrong' })
const statusDisplay = document.getElementById('watchlist-status-display-' + id) const statusDisplay = document.getElementById('watchlist-status-display-' + id)
if (statusDisplay) { if (statusDisplay) {
statusDisplay.textContent = inWatchlist ? 'Plan to Watch' : 'Add to Watchlist' statusDisplay.textContent = inWatchlist ? 'Plan to Watch' : 'Add to Watchlist'
const removeContainer = document.getElementById('remove-watchlist-container-' + id) syncRemoveButtonVisibility(id)
if (removeContainer) {
removeContainer.classList.toggle('hidden', !inWatchlist)
}
} }
} }
@@ -186,6 +196,57 @@ if (window.showToast) showToast({ message: 'Something went wrong' })
} }
}) })
} }
function updateWatchlist(id, status, display, btn) {
fetch('/api/watchlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ animeId: id, status: status })
}).then(res => {
if (res.ok) {
watchlistIds.add(id);
document.getElementById('watchlist-status-display-' + id).textContent = display;
syncRemoveButtonVisibility(id);
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button');
if (button) {
const malId = button.dataset.malId;
if (malId && parseInt(malId) === id) {
button.classList.add('in-watchlist');
}
}
});
requestAnimationFrame(() => {
const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close();
});
}
});
}
function removeWatchlist(id, btn) {
fetch('/api/watchlist/' + id, { method: 'DELETE' }).then(res => {
if (res.ok) {
watchlistIds.delete(id);
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
syncRemoveButtonVisibility(id);
if (window.showToast) showToast({ message: 'Removed from watchlist' });
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button');
if (button) {
const malId = button.dataset.malId;
if (malId && parseInt(malId) === id) {
button.classList.remove('in-watchlist');
}
}
});
if (btn) {
const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close();
}
}
});
}
</script> </script>
</head> </head>
<body class="bg-background text-foreground"> <body class="bg-background text-foreground">

View File

@@ -38,7 +38,7 @@
<div id="remove-watchlist-container-{{$anime.MalID}}" class="{{if not $status}}hidden{{end}}"> <div id="remove-watchlist-container-{{$anime.MalID}}" class="{{if not $status}}hidden{{end}}">
<div class="my-1 h-px bg-border"></div> <div class="my-1 h-px bg-border"></div>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-red-500/10 focus:bg-red-500/10" onclick="removeWatchlist({{$anime.MalID}})"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-red-500/10 focus:bg-red-500/10" onclick="removeWatchlist({{$anime.MalID}}, this)">
<span class="font-medium text-sm text-red-500 text-left whitespace-nowrap">Remove from Watchlist</span> <span class="font-medium text-sm text-red-500 text-left whitespace-nowrap">Remove from Watchlist</span>
</button> </button>
</div> </div>
@@ -50,65 +50,4 @@
{{if and .ContinueWatchingEp (ne .ContinueWatchingEp 1)}}Continue Episode {{.ContinueWatchingEp}}{{else}}Watch Now{{end}} {{if and .ContinueWatchingEp (ne .ContinueWatchingEp 1)}}Continue Episode {{.ContinueWatchingEp}}{{else}}Watch Now{{end}}
</a> </a>
</div> </div>
{{end}}
<script>
function updateWatchlist(id, status, display, btn) {
fetch('/api/watchlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ animeId: id, status: status })
}).then(res => {
if (res.ok) {
watchlistIds.add(id);
document.getElementById('watchlist-status-display-' + id).textContent = display;
document.getElementById('remove-watchlist-container-' + id).classList.remove('hidden');
// Update all watchlist icons on the page
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button')
if (button) {
const malId = button.dataset.malId
if (malId && parseInt(malId) === id) {
button.classList.add('in-watchlist')
}
}
});
// Close dropdown after a small delay to let click event finish
requestAnimationFrame(() => {
const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close();
});
}
});
}
function removeWatchlist(id) {
fetch('/api/watchlist/' + id, { method: 'DELETE' }).then(res => {
if (res.ok) {
watchlistIds.delete(id);
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
document.getElementById('remove-watchlist-container-' + id).classList.add('hidden');
// Update all watchlist icons on the page
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button')
if (button) {
const malId = button.dataset.malId
if (malId && parseInt(malId) === id) {
button.classList.remove('in-watchlist')
}
}
});
// Close dropdown
const btn = document.getElementById('watchlist-status-display-' + id);
if (btn) {
const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close();
}
}
});
}
</script>
{{end}}

View File

@@ -164,72 +164,6 @@
{{end}} {{end}}
{{end}} {{end}}
<script>
document.addEventListener('DOMContentLoaded', function() {
if (watchlistIds.has({{$anime.MalID}})) {
document.getElementById('remove-watchlist-container-{{$anime.MalID}}').classList.remove('hidden');
}
});
function updateWatchlist(id, status, display, btn) {
fetch('/api/watchlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ animeId: id, status: status })
}).then(res => {
if (res.ok) {
watchlistIds.add(id);
document.getElementById('watchlist-status-display-' + id).textContent = display;
document.getElementById('remove-watchlist-container-' + id).classList.remove('hidden');
// Update all watchlist icons on the page
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button')
if (button) {
const malId = button.dataset.malId
if (malId && parseInt(malId) === id) {
button.classList.add('in-watchlist')
}
}
});
// Close dropdown after a small delay to let click event finish
requestAnimationFrame(() => {
const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close();
});
}
});
}
function removeWatchlist(id, btn) {
fetch('/api/watchlist/' + id, { method: 'DELETE' }).then(res => {
if (res.ok) {
watchlistIds.delete(id);
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
document.getElementById('remove-watchlist-container-' + id).classList.add('hidden');
if (window.showToast) showToast({ message: 'Removed from watchlist' });
// Update all watchlist icons on the page
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button')
if (button) {
const malId = button.dataset.malId
if (malId && parseInt(malId) === id) {
button.classList.remove('in-watchlist')
}
}
});
// Close dropdown
if (btn) {
const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close();
}
}
});
}
</script>
</div> </div>
</div> </div>
{{end}} {{end}}