feat: natural toast messages with anime title

This commit is contained in:
2026-05-15 02:22:20 +02:00
parent 74e2aa50fd
commit aa8df4fd54
4 changed files with 36 additions and 20 deletions

View File

@@ -31,13 +31,15 @@
html[data-theme="dark"] .theme-icon-dark { display: none; } html[data-theme="dark"] .theme-icon-dark { display: none; }
html[data-theme="dark"] .theme-icon-light { display: block; } html[data-theme="dark"] .theme-icon-light { display: block; }
html[data-theme="light"] .theme-icon-light { display: none; } html[data-theme="light"] .theme-icon-light { display: none; }
html[data-theme="light"] .theme-icon-dark { display: block; } html[data-theme="light"] .theme-icon-dark { display: block; }
</style> </style>
<script type="module" src="/dist/static/theme.js" defer></script> <script type="module" src="/dist/static/theme.js" defer></script>
<template id="toast-template"> <template id="toast-template">
<div class="toast bg-foreground/10 border border-border flex items-center gap-3 px-4 py-3 shadow-lg transform transition-all duration-300 translate-y-2 opacity-0"> <div class="toast pointer-events-auto w-[22rem] max-w-[calc(100vw-2rem)] bg-background shadow-2xl ring-1 ring-black/5 flex items-start gap-3 px-4 py-3 transform transition-all duration-300 translate-y-2 opacity-0">
<span class="toast-message text-sm text-foreground"></span> <div class="min-w-0 flex-1">
<button class="toast-close ml-2 opacity-50 hover:opacity-100" aria-label="Close"> <div class="toast-message text-sm font-medium text-foreground leading-snug"></div>
</div>
<button class="toast-close -mr-1 -mt-1 rounded-md p-1.5 opacity-70 hover:opacity-100 hover:bg-foreground/10 focus:outline-none focus:ring-2 focus:ring-ring" aria-label="Close">
<svg class="size-4 text-foreground-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg class="size-4 text-foreground-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/> <path d="M18 6L6 18M6 6l12 12"/>
</svg> </svg>
@@ -101,7 +103,7 @@
} }
} }
function toggleWatchlist(id, btn) { function toggleWatchlist(id, title, btn) {
// determine action based on current watchlist state // determine action based on current watchlist state
const isInWatchlist = watchlistIds.has(id) const isInWatchlist = watchlistIds.has(id)
const url = isInWatchlist ? `/api/watchlist/${id}` : '/api/watchlist' const url = isInWatchlist ? `/api/watchlist/${id}` : '/api/watchlist'
@@ -118,7 +120,7 @@
watchlistIds.delete(id) watchlistIds.delete(id)
btn.classList.remove('in-watchlist') btn.classList.remove('in-watchlist')
btn.setAttribute('aria-label', 'Add to Watchlist') btn.setAttribute('aria-label', 'Add to Watchlist')
if (window.showToast) showToast({ message: 'Removed from watchlist' }) if (window.showToast) showToast({ message: `Removed ${title} from watchlist` })
// Update dropdown status if on anime page // Update dropdown status if on anime page
syncWatchlistDropdown(id, false) syncWatchlistDropdown(id, false)
@@ -126,7 +128,7 @@
watchlistIds.add(id) watchlistIds.add(id)
btn.classList.add('in-watchlist') btn.classList.add('in-watchlist')
btn.setAttribute('aria-label', 'Remove from Watchlist') btn.setAttribute('aria-label', 'Remove from Watchlist')
if (window.showToast) showToast({ message: 'Added to watchlist' }) if (window.showToast) showToast({ message: `Added ${title} to watchlist` })
// Update dropdown status if on anime page // Update dropdown status if on anime page
syncWatchlistDropdown(id, true) syncWatchlistDropdown(id, true)
@@ -168,12 +170,15 @@ if (window.showToast) showToast({ message: 'Something went wrong' })
watchlistIds.delete(id) watchlistIds.delete(id)
const card = btn.closest('.group').parentElement const card = btn.closest('.group').parentElement
if (card) card.remove() if (card) card.remove()
if (window.showToast) showToast({ message: 'Removed from watchlist' })
setTimeout(() => location.reload(), 100) setTimeout(() => location.reload(), 100)
} else {
if (window.showToast) showToast({ message: 'Failed to update watchlist' })
} }
}) })
} }
function updateWatchlist(id, status, display, btn) { function updateWatchlist(id, status, display, title, btn) {
fetch('/api/watchlist', { fetch('/api/watchlist', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -196,17 +201,22 @@ if (window.showToast) showToast({ message: 'Something went wrong' })
const dropdown = btn.closest('ui-dropdown'); const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close(); if (dropdown) dropdown.close();
}); });
if (window.showToast) showToast({ message: `Marked ${title} as ${display}` });
} else {
if (window.showToast) showToast({ message: 'Failed to update watchlist' });
} }
}).catch(() => {
if (window.showToast) showToast({ message: 'Something went wrong' });
}); });
} }
function removeWatchlist(id, btn) { function removeWatchlist(id, title, btn) {
fetch('/api/watchlist/' + id, { method: 'DELETE' }).then(res => { fetch('/api/watchlist/' + id, { method: 'DELETE' }).then(res => {
if (res.ok) { if (res.ok) {
watchlistIds.delete(id); watchlistIds.delete(id);
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist'; document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
syncRemoveButtonVisibility(id); syncRemoveButtonVisibility(id);
if (window.showToast) showToast({ message: 'Removed from watchlist' }); if (window.showToast) showToast({ message: `Removed ${title} from watchlist` });
document.querySelectorAll('.watchlist-icon').forEach(function(icon) { document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
const button = icon.closest('button'); const button = icon.closest('button');
if (button) { if (button) {
@@ -220,7 +230,11 @@ if (window.showToast) showToast({ message: 'Something went wrong' })
const dropdown = btn.closest('ui-dropdown'); const dropdown = btn.closest('ui-dropdown');
if (dropdown) dropdown.close(); if (dropdown) dropdown.close();
} }
} else {
if (window.showToast) showToast({ message: 'Failed to update watchlist' });
} }
}).catch(() => {
if (window.showToast) showToast({ message: 'Something went wrong' });
}); });
} }
</script> </script>

View File

@@ -35,7 +35,7 @@
{{end}} {{end}}
<div class="mt-auto flex items-center justify-start pb-2 pl-2"> <div class="mt-auto flex items-center justify-start pb-2 pl-2">
<button type="button" data-mal-id="{{$anime.MalID}}" onclick="event.preventDefault(); event.stopPropagation(); toggleWatchlist({{$anime.MalID}}, this)" class="text-accent hover:text-accent/80 transition-colors focus:outline-none {{if $isWatchlist}}in-watchlist{{end}}" aria-label="{{if $isWatchlist}}Remove from Watchlist{{else}}Add to Watchlist{{end}}"> <button type="button" data-mal-id="{{$anime.MalID}}" onclick="event.preventDefault(); event.stopPropagation(); toggleWatchlist({{$anime.MalID}}, '{{$anime.DisplayTitle}}', this)" class="text-accent hover:text-accent/80 transition-colors focus:outline-none {{if $isWatchlist}}in-watchlist{{end}}" aria-label="{{if $isWatchlist}}Remove from Watchlist{{else}}Add to Watchlist{{end}}">
<svg class="size-6 shadow-black drop-shadow-md watchlist-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg> <svg class="size-6 shadow-black drop-shadow-md watchlist-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg>
</button> </button>
</div> </div>

View File

@@ -23,21 +23,22 @@
<div data-content class="hidden absolute z-50 min-w-[160px] bg-background-button rounded-none shadow-2xl left-0 top-full mt-2"> <div data-content class="hidden absolute z-50 min-w-[160px] bg-background-button rounded-none shadow-2xl left-0 top-full mt-2">
<div class="flex flex-col py-1"> <div class="flex flex-col py-1">
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'watching', 'Watching', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'watching', 'Watching', '{{$anime.DisplayTitle}}', this)">
<span class="font-medium text-sm text-foreground">Watching</span> <span class="font-medium text-sm text-foreground">Watching</span>
</button> </button>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'completed', 'Completed', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'completed', 'Completed', '{{$anime.DisplayTitle}}', this)">
<span class="font-medium text-sm text-foreground">Completed</span> <span class="font-medium text-sm text-foreground">Completed</span>
</button> </button>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'plan_to_watch', 'Plan to Watch', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'plan_to_watch', 'Plan to Watch', '{{$anime.DisplayTitle}}', this)">
<span class="font-medium text-sm text-foreground">Plan to Watch</span> <span class="font-medium text-sm text-foreground">Plan to Watch</span>
</button> </button>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'dropped', 'Dropped', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-foreground/10 focus:bg-foreground/10" onclick="updateWatchlist({{$anime.MalID}}, 'dropped', 'Dropped', '{{$anime.DisplayTitle}}', this)">
<span class="font-medium text-sm text-foreground">Dropped</span> <span class="font-medium text-sm text-foreground">Dropped</span>
</button> </button>
{{template "watchlist_remove_button" dict {{template "watchlist_remove_button" dict
"ID" $anime.MalID "ID" $anime.MalID
"Title" $anime.DisplayTitle
"ContainerClass" "hidden" "ContainerClass" "hidden"
"DividerClass" "my-1 h-px bg-border" "DividerClass" "my-1 h-px bg-border"
"ButtonClass" "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" "ButtonClass" "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"
@@ -55,7 +56,7 @@
{{define "watchlist_remove_button"}} {{define "watchlist_remove_button"}}
<div id="remove-watchlist-container-{{.ID}}" class="{{.ContainerClass}}"> <div id="remove-watchlist-container-{{.ID}}" class="{{.ContainerClass}}">
<button class="{{.ButtonClass}}" onclick="removeWatchlist({{.ID}}, this)"> <button class="{{.ButtonClass}}" onclick="removeWatchlist({{.ID}}, '{{.Title}}', this)">
<span class="{{.SpanClass}}">Remove from Watchlist</span> <span class="{{.SpanClass}}">Remove from Watchlist</span>
</button> </button>
</div> </div>

View File

@@ -25,20 +25,21 @@
</div> </div>
<div data-content class="hidden absolute z-50 min-w-40 bg-background-button shadow-2xl right-0 top-full mt-2"> <div data-content class="hidden absolute z-50 min-w-40 bg-background-button shadow-2xl right-0 top-full mt-2">
<div class="flex flex-col py-1"> <div class="flex flex-col py-1">
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'watching', 'Watching', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'watching', 'Watching', '{{$anime.Title}}', this)">
<span class="text-sm text-foreground">Watching</span> <span class="text-sm text-foreground">Watching</span>
</button> </button>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'completed', 'Completed', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'completed', 'Completed', '{{$anime.Title}}', this)">
<span class="text-sm text-foreground">Completed</span> <span class="text-sm text-foreground">Completed</span>
</button> </button>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'plan_to_watch', 'Plan to Watch', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'plan_to_watch', 'Plan to Watch', '{{$anime.Title}}', this)">
<span class="text-sm text-foreground">Plan to Watch</span> <span class="text-sm text-foreground">Plan to Watch</span>
</button> </button>
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'dropped', 'Dropped', this)"> <button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-surface-hover" onclick="updateWatchlist({{$anime.MalID}}, 'dropped', 'Dropped', '{{$anime.Title}}', this)">
<span class="text-sm text-foreground">Dropped</span> <span class="text-sm text-foreground">Dropped</span>
</button> </button>
{{template "watchlist_remove_button" dict {{template "watchlist_remove_button" dict
"ID" $anime.MalID "ID" $anime.MalID
"Title" $anime.Title
"ContainerClass" "hidden" "ContainerClass" "hidden"
"DividerClass" "border-t border-border my-1" "DividerClass" "border-t border-border my-1"
"ButtonClass" "flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-red-500/10" "ButtonClass" "flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-red-500/10"