feat: show in-progress books under 'Continue reading' on home page

Reading progress lives in localStorage so sorting is done client-side.
On page load, JS reads the reading_progress map, moves any matching book
cards into a 'Continue reading' grid above the main grid, and reveals an
'Available' heading for the remainder. Books with no saved progress are
unaffected and no server changes are required.
This commit is contained in:
Admin
2026-03-01 17:24:55 +05:00
parent e0dc4678b3
commit 07fbfe5555

View File

@@ -146,34 +146,73 @@ const homeTmpl = `
<div id="scrape-status" class="mt-3"></div> <div id="scrape-status" class="mt-3"></div>
</div> </div>
<!-- Book grid --> <!-- Continue reading section (populated by JS) -->
<div class="grid gap-4 sm:grid-cols-2"> <div id="continue-reading-section" class="hidden mb-10">
{{range .Books}} <h2 class="text-lg font-semibold text-zinc-200 mb-3">Continue reading</h2>
<a href="/books/{{.Slug}}" <div id="continue-reading-grid" class="grid gap-4 sm:grid-cols-2"></div>
hx-get="/books/{{.Slug}}" </div>
hx-target="#main-content"
hx-push-url="true" <!-- All books grid -->
hx-swap="innerHTML" <div id="available-section">
class="group block rounded-xl border border-zinc-800 bg-zinc-900 p-5 hover:border-amber-500 transition-colors cursor-pointer"> <h2 id="available-heading" class="text-lg font-semibold text-zinc-200 mb-3 hidden">Available</h2>
<div class="flex gap-4"> <div id="books-grid" class="grid gap-4 sm:grid-cols-2">
{{if .Cover}} {{range .Books}}
<img src="{{.Cover}}" alt="cover" class="w-14 h-20 object-cover rounded flex-shrink-0"> <a href="/books/{{.Slug}}"
{{end}} hx-get="/books/{{.Slug}}"
<div class="min-w-0"> hx-target="#main-content"
<h2 class="font-semibold text-zinc-100 group-hover:text-amber-400 truncate">{{.Title}}</h2> hx-push-url="true"
{{if .Author}}<p class="text-sm text-zinc-400 mt-0.5">{{.Author}}</p>{{end}} hx-swap="innerHTML"
<div class="flex gap-2 mt-2 flex-wrap"> data-slug="{{.Slug}}"
{{if .Status}}<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800 text-zinc-300">{{.Status}}</span>{{end}} class="book-card group block rounded-xl border border-zinc-800 bg-zinc-900 p-5 hover:border-amber-500 transition-colors cursor-pointer">
{{if .TotalChapters}}<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800 text-zinc-300">{{.TotalChapters}} ch</span>{{end}} <div class="flex gap-4">
{{if .Cover}}
<img src="{{.Cover}}" alt="cover" class="w-14 h-20 object-cover rounded flex-shrink-0">
{{end}}
<div class="min-w-0">
<h2 class="font-semibold text-zinc-100 group-hover:text-amber-400 truncate">{{.Title}}</h2>
{{if .Author}}<p class="text-sm text-zinc-400 mt-0.5">{{.Author}}</p>{{end}}
<div class="flex gap-2 mt-2 flex-wrap">
{{if .Status}}<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800 text-zinc-300">{{.Status}}</span>{{end}}
{{if .TotalChapters}}<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-800 text-zinc-300">{{.TotalChapters}} ch</span>{{end}}
</div>
</div> </div>
</div> </div>
</div> </a>
</a> {{else}}
{{else}} <p class="text-zinc-500 col-span-2">No books scraped yet.</p>
<p class="text-zinc-500 col-span-2">No books scraped yet.</p> {{end}}
{{end}} </div>
</div> </div>
</div>` </div>
<script>
(function () {
var progress = {};
try { progress = JSON.parse(localStorage.getItem('reading_progress') || '{}'); } catch(_) {}
var inProgress = Object.keys(progress);
if (inProgress.length === 0) return;
var continueSection = document.getElementById('continue-reading-section');
var continueGrid = document.getElementById('continue-reading-grid');
var availableHeading = document.getElementById('available-heading');
var moved = 0;
inProgress.forEach(function(slug) {
var card = document.querySelector('[data-slug="' + slug + '"]');
if (!card) return;
var clone = card.cloneNode(true);
continueGrid.appendChild(clone);
card.parentNode.removeChild(card);
moved++;
});
if (moved > 0) {
continueSection.classList.remove('hidden');
availableHeading.classList.remove('hidden');
}
}());
</script>`
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) { func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" { if r.URL.Path != "/" {