fix: sync server-rendered watchlist state to client
This commit is contained in:
@@ -58,3 +58,13 @@ body {
|
|||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
color: var(--text);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const syncIconsForId = (id: number): void => {
|
|||||||
const malId = toInt(button.dataset.malId);
|
const malId = toInt(button.dataset.malId);
|
||||||
if (malId !== id) return;
|
if (malId !== id) return;
|
||||||
button.classList.toggle('in-watchlist', shouldBeInWatchlist);
|
button.classList.toggle('in-watchlist', shouldBeInWatchlist);
|
||||||
|
button.dataset.watchlistState = shouldBeInWatchlist ? 'in' : 'out';
|
||||||
button.setAttribute(
|
button.setAttribute(
|
||||||
'aria-label',
|
'aria-label',
|
||||||
shouldBeInWatchlist ? 'Remove from Watchlist' : 'Add to Watchlist'
|
shouldBeInWatchlist ? 'Remove from Watchlist' : 'Add to Watchlist'
|
||||||
@@ -109,8 +110,18 @@ const closeClosestDropdown = (from: HTMLElement): void => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleWatchlist = async (id: number, title: string): Promise<void> => {
|
const toggleWatchlist = async (
|
||||||
|
id: number,
|
||||||
|
title: string,
|
||||||
|
renderedState: string | undefined
|
||||||
|
): Promise<void> => {
|
||||||
if (inflight.has(id)) return;
|
if (inflight.has(id)) return;
|
||||||
|
if (renderedState === 'in') {
|
||||||
|
watchlistIds.add(id);
|
||||||
|
} else if (renderedState === 'out') {
|
||||||
|
watchlistIds.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
const isInWatchlist = watchlistIds.has(id);
|
const isInWatchlist = watchlistIds.has(id);
|
||||||
|
|
||||||
setBusy(id, true);
|
setBusy(id, true);
|
||||||
@@ -250,6 +261,20 @@ const initWatchlist = (ids: number[]): void => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRenderedWatchlistIds = (): number[] => {
|
||||||
|
const ids = new Set<number>();
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelectorAll<HTMLElement>('[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 => {
|
const onReady = (fn: () => void): void => {
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', fn, { once: true });
|
document.addEventListener('DOMContentLoaded', fn, { once: true });
|
||||||
@@ -272,7 +297,7 @@ const installDelegatedHandlers = (): void => {
|
|||||||
const id = toInt(toggleButton.getAttribute('data-mal-id') ?? undefined);
|
const id = toInt(toggleButton.getAttribute('data-mal-id') ?? undefined);
|
||||||
if (id === null) return;
|
if (id === null) return;
|
||||||
const title = toggleButton.getAttribute('data-watchlist-title') ?? 'anime';
|
const title = toggleButton.getAttribute('data-watchlist-title') ?? 'anime';
|
||||||
void toggleWatchlist(id, title);
|
void toggleWatchlist(id, title, toggleButton.dataset.watchlistState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,5 +347,10 @@ onReady(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderedWatchlistIds = getRenderedWatchlistIds();
|
||||||
|
if (renderedWatchlistIds.length > 0) {
|
||||||
|
initWatchlist(renderedWatchlistIds);
|
||||||
|
}
|
||||||
|
|
||||||
installDelegatedHandlers();
|
installDelegatedHandlers();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
data-watchlist-toggle
|
data-watchlist-toggle
|
||||||
data-mal-id="{{$anime.MalID}}"
|
data-mal-id="{{$anime.MalID}}"
|
||||||
data-watchlist-title="{{$anime.DisplayTitle}}"
|
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}}"
|
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}}"
|
aria-label="{{if $isWatchlist}}Remove from Watchlist{{else}}Add to Watchlist{{end}}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
data-watchlist-toggle
|
data-watchlist-toggle
|
||||||
data-mal-id="{{$anime.MalID}}"
|
data-mal-id="{{$anime.MalID}}"
|
||||||
data-watchlist-title="{{$anime.DisplayTitle}}"
|
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}}"
|
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}}"
|
aria-label="{{if (index $.WatchlistMap $anime.MalID)}}Remove from Watchlist{{else}}Add to Watchlist{{end}}"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user