diff --git a/internal/anime/handler/handler.go b/internal/anime/handler/handler.go index 902bf87..d13a9d2 100644 --- a/internal/anime/handler/handler.go +++ b/internal/anime/handler/handler.go @@ -316,8 +316,8 @@ func (h *AnimeHandler) HandleScheduleSection(c *gin.Context) { watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, animes) c.HTML(http.StatusOK, "schedule.gohtml", gin.H{ - "_fragment": "schedule_section", - "Animes": animes, + "_fragment": "schedule_section", + "Animes": animes, "WatchlistMap": watchlistMap, }) } diff --git a/static/assets/style.css b/static/assets/style.css index 8977568..848d8e6 100644 --- a/static/assets/style.css +++ b/static/assets/style.css @@ -58,3 +58,13 @@ body { background-color: var(--color-background); color: var(--text); } + +[data-watchlist-toggle] .watchlist-icon, +[data-watchlist-toggle] .watchlist-icon path { + fill: none; +} + +[data-watchlist-toggle][data-watchlist-state='in'] .watchlist-icon, +[data-watchlist-toggle][data-watchlist-state='in'] .watchlist-icon path { + fill: currentColor; +} diff --git a/static/watchlist.ts b/static/watchlist.ts index e0951b9..60807bb 100644 --- a/static/watchlist.ts +++ b/static/watchlist.ts @@ -66,6 +66,7 @@ const syncIconsForId = (id: number): void => { const malId = toInt(button.dataset.malId); if (malId !== id) return; button.classList.toggle('in-watchlist', shouldBeInWatchlist); + button.dataset.watchlistState = shouldBeInWatchlist ? 'in' : 'out'; button.setAttribute( 'aria-label', shouldBeInWatchlist ? 'Remove from Watchlist' : 'Add to Watchlist' @@ -109,8 +110,18 @@ const closeClosestDropdown = (from: HTMLElement): void => { }); }; -const toggleWatchlist = async (id: number, title: string): Promise => { +const toggleWatchlist = async ( + id: number, + title: string, + renderedState: string | undefined +): Promise => { if (inflight.has(id)) return; + if (renderedState === 'in') { + watchlistIds.add(id); + } else if (renderedState === 'out') { + watchlistIds.delete(id); + } + const isInWatchlist = watchlistIds.has(id); setBusy(id, true); @@ -250,6 +261,20 @@ const initWatchlist = (ids: number[]): void => { }); }; +const getRenderedWatchlistIds = (): number[] => { + const ids = new Set(); + + document + .querySelectorAll('[data-watchlist-toggle][data-watchlist-state="in"][data-mal-id]') + .forEach(button => { + const id = toInt(button.dataset.malId); + if (id === null) return; + ids.add(id); + }); + + return Array.from(ids); +}; + const onReady = (fn: () => void): void => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fn, { once: true }); @@ -272,7 +297,7 @@ const installDelegatedHandlers = (): void => { const id = toInt(toggleButton.getAttribute('data-mal-id') ?? undefined); if (id === null) return; const title = toggleButton.getAttribute('data-watchlist-title') ?? 'anime'; - void toggleWatchlist(id, title); + void toggleWatchlist(id, title, toggleButton.dataset.watchlistState); return; } @@ -322,5 +347,10 @@ onReady(() => { } } + const renderedWatchlistIds = getRenderedWatchlistIds(); + if (renderedWatchlistIds.length > 0) { + initWatchlist(renderedWatchlistIds); + } + installDelegatedHandlers(); }); diff --git a/templates/components/anime_card.gohtml b/templates/components/anime_card.gohtml index 19a5447..08418b6 100644 --- a/templates/components/anime_card.gohtml +++ b/templates/components/anime_card.gohtml @@ -43,6 +43,7 @@ data-watchlist-toggle data-mal-id="{{$anime.MalID}}" data-watchlist-title="{{$anime.DisplayTitle}}" + data-watchlist-state="{{if $isWatchlist}}in{{else}}out{{end}}" class="text-accent hover:text-accent/80 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:opacity-50 {{if $isWatchlist}}in-watchlist{{end}}" aria-label="{{if $isWatchlist}}Remove from Watchlist{{else}}Add to Watchlist{{end}}" > diff --git a/templates/schedule.gohtml b/templates/schedule.gohtml index e03e5d5..3136fc1 100644 --- a/templates/schedule.gohtml +++ b/templates/schedule.gohtml @@ -55,6 +55,7 @@ data-watchlist-toggle data-mal-id="{{$anime.MalID}}" data-watchlist-title="{{$anime.DisplayTitle}}" + data-watchlist-state="{{if (index $.WatchlistMap $anime.MalID)}}in{{else}}out{{end}}" class="shrink-0 text-accent hover:text-accent/80 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:opacity-50 {{if (index $.WatchlistMap $anime.MalID)}}in-watchlist{{end}}" aria-label="{{if (index $.WatchlistMap $anime.MalID)}}Remove from Watchlist{{else}}Add to Watchlist{{end}}" >