From 4c23a7722af2f6777af15dc147e739812ec04d7f Mon Sep 17 00:00:00 2001 From: mi Date: Thu, 6 Nov 2025 22:48:39 +1000 Subject: [PATCH] :lightning: client-side nav --- README.md | 5 ++ static/js/comic-nav.js | 151 +++++++++++++++++++++++++++++++++++++++++ templates/base.html | 1 + templates/comic.html | 2 +- templates/index.html | 2 +- 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 static/js/comic-nav.js diff --git a/README.md b/README.md index 3848171..075ee8f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,10 @@ A Flask-based webcomic website with server-side rendering using Jinja2 templates ## Features - Comic viewer with navigation (First, Previous, Next, Latest) +- Client-side navigation using JSON API (no page reloads) - Archive page with thumbnail grid +- RSS feed support +- JSON API for programmatic access - Responsive design - Server-side rendering with Jinja2 - Clean, comic-focused layout @@ -33,6 +36,8 @@ sunday/ └── static/ # Static files ├── css/ │ └── style.css # Main stylesheet + ├── js/ + │ └── comic-nav.js # Client-side navigation ├── images/ # Comic images │ └── thumbs/ # Thumbnail images for archive └── feed.rss # RSS feed (generated) diff --git a/static/js/comic-nav.js b/static/js/comic-nav.js new file mode 100644 index 0000000..7a89329 --- /dev/null +++ b/static/js/comic-nav.js @@ -0,0 +1,151 @@ +// Client-side comic navigation using the API +(function() { + 'use strict'; + + let totalComics = 0; + + // Fetch and display a comic + async function loadComic(comicId) { + try { + const response = await fetch(`/api/comics/${comicId}`); + + if (!response.ok) { + if (response.status === 404) { + window.location.href = '/404'; + return; + } + throw new Error('Failed to load comic'); + } + + const comic = response.json ? await response.json() : comic; + displayComic(comic); + + } catch (error) { + console.error('Error loading comic:', error); + } + } + + // Update the page with comic data + function displayComic(comic) { + // Update title + const title = comic.title || `#${comic.number}`; + document.querySelector('.comic-header h1').textContent = title; + + // Update date + document.querySelector('.comic-date').textContent = comic.date; + + // Update image + const img = document.querySelector('.comic-image img'); + img.src = `/static/images/${comic.filename}`; + img.alt = title; + img.title = comic.alt_text; + + // Update author note + const transcriptDiv = document.querySelector('.comic-transcript'); + if (comic.author_note) { + if (!transcriptDiv) { + const container = document.querySelector('.comic-container'); + const newDiv = document.createElement('div'); + newDiv.className = 'comic-transcript'; + newDiv.innerHTML = '

Author Note

'; + container.appendChild(newDiv); + } + document.querySelector('.comic-transcript p').textContent = comic.author_note; + document.querySelector('.comic-transcript').style.display = 'block'; + } else if (transcriptDiv) { + transcriptDiv.style.display = 'none'; + } + + // Update navigation buttons + updateNavButtons(comic.number); + + // Update page title + document.title = `${title} - Sunday Comics`; + + // Update URL without reload + history.pushState({ comicId: comic.number }, '', `/comic/${comic.number}`); + } + + // Update navigation button states + function updateNavButtons(currentNumber) { + const navButtons = document.querySelector('.nav-buttons'); + + // First button + const firstBtn = navButtons.children[0]; + if (currentNumber > 1) { + firstBtn.className = 'btn btn-nav'; + firstBtn.onclick = (e) => { e.preventDefault(); loadComic(1); }; + } else { + firstBtn.className = 'btn btn-nav btn-disabled'; + firstBtn.onclick = null; + } + + // Previous button + const prevBtn = navButtons.children[1]; + if (currentNumber > 1) { + prevBtn.className = 'btn btn-nav'; + prevBtn.onclick = (e) => { e.preventDefault(); loadComic(currentNumber - 1); }; + } else { + prevBtn.className = 'btn btn-nav btn-disabled'; + prevBtn.onclick = null; + } + + // Comic number display + navButtons.children[2].textContent = `Comic #${currentNumber}`; + + // Next button + const nextBtn = navButtons.children[3]; + if (currentNumber < totalComics) { + nextBtn.className = 'btn btn-nav'; + nextBtn.onclick = (e) => { e.preventDefault(); loadComic(currentNumber + 1); }; + } else { + nextBtn.className = 'btn btn-nav btn-disabled'; + nextBtn.onclick = null; + } + + // Latest button + const latestBtn = navButtons.children[4]; + if (currentNumber < totalComics) { + latestBtn.className = 'btn btn-nav'; + latestBtn.onclick = (e) => { e.preventDefault(); loadComic(totalComics); }; + } else { + latestBtn.className = 'btn btn-nav btn-disabled'; + latestBtn.onclick = null; + } + } + + // Initialize on page load + async function init() { + // Only run on comic pages + if (!document.querySelector('.comic-container')) { + return; + } + + // Get total comics count from the page + totalComics = parseInt(document.querySelector('.comic-container').dataset.totalComics || 0); + + // Get current comic number + const currentNumber = parseInt(document.querySelector('.comic-container').dataset.comicNumber || 0); + + if (currentNumber && totalComics) { + updateNavButtons(currentNumber); + } + + // Handle browser back/forward + window.addEventListener('popstate', (event) => { + if (event.state && event.state.comicId) { + loadComic(event.state.comicId); + } + }); + + // Set initial state + history.replaceState({ comicId: currentNumber }, '', window.location.pathname); + } + + // Run when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/templates/base.html b/templates/base.html index 6eba9bb..f4a061e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -61,6 +61,7 @@ + {% block extra_js %}{% endblock %} \ No newline at end of file diff --git a/templates/comic.html b/templates/comic.html index 5a63ab6..604f396 100644 --- a/templates/comic.html +++ b/templates/comic.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -
+

{{ comic.title if comic.title else '#' ~ comic.number }}

{{ comic.date }}

diff --git a/templates/index.html b/templates/index.html index 446ed3c..ab96381 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -
+

{{ comic.title if comic.title else '#' ~ comic.number }}

{{ comic.date }}