From 7a9f64ee176cf2a28a9d234c953e4b9956cba701 Mon Sep 17 00:00:00 2001 From: mi Date: Fri, 14 Nov 2025 15:43:28 +1000 Subject: [PATCH] :wheelchair: keyboard nav --- README.md | 20 +++++++++++++++++++ static/js/comic-nav.js | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/README.md b/README.md index fc6893f..7877cb9 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Don't have a server? No problem! Here are beginner-friendly options to get your - Comic viewer with navigation (First, Previous, Next, Latest) - Client-side navigation using JSON API (no page reloads) +- Keyboard navigation support (arrow keys, Home/End) - Archive page with thumbnail grid - RSS feed support - Markdown support for author notes and about page @@ -389,6 +390,25 @@ SOCIAL_YOUTUBE = 'https://youtube.com/@yourchannel' SOCIAL_EMAIL = 'mailto:your@email.com' ``` +## Navigation + +Sunday Comics provides multiple ways for readers to navigate through your comic: + +### Mouse/Touch Navigation +- **Navigation buttons**: First, Previous, Next, Latest buttons (text or icon-based) +- **Click-through**: Click on the comic image to advance to the next comic +- **Archive grid**: Click any thumbnail to jump directly to that comic + +### Keyboard Navigation +When viewing a comic, readers can use keyboard shortcuts for quick navigation: + +- **Left Arrow** (`←`) - Go to previous comic +- **Right Arrow** (`→`) - Go to next comic +- **Home** - Jump to first comic +- **End** - Jump to latest comic + +Keyboard shortcuts respect navigation boundaries (won't navigate before the first comic or past the latest) and don't interfere with typing in input fields. + ## Pages - `/` - Shows the latest comic diff --git a/static/js/comic-nav.js b/static/js/comic-nav.js index 2284d5b..dd81b1b 100644 --- a/static/js/comic-nav.js +++ b/static/js/comic-nav.js @@ -4,6 +4,7 @@ let totalComics = 0; let comicName = ''; // Will be extracted from initial page title + let currentComicNumber = 0; // Fetch and display a comic async function loadComic(comicId) { @@ -29,6 +30,7 @@ // Update the page with comic data function displayComic(comic) { const title = comic.title || `#${comic.number}`; + currentComicNumber = comic.number; // Update container class for full-width option const container = document.querySelector('.comic-container'); @@ -227,6 +229,45 @@ } } + // Handle keyboard navigation + function handleKeyboardNavigation(event) { + // Don't interfere if user is typing in an input field + if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') { + return; + } + + switch(event.key) { + case 'ArrowLeft': + // Previous comic + if (currentComicNumber > 1) { + event.preventDefault(); + loadComic(currentComicNumber - 1); + } + break; + case 'ArrowRight': + // Next comic + if (currentComicNumber < totalComics) { + event.preventDefault(); + loadComic(currentComicNumber + 1); + } + break; + case 'Home': + // First comic + if (currentComicNumber > 1) { + event.preventDefault(); + loadComic(1); + } + break; + case 'End': + // Latest comic + if (currentComicNumber < totalComics) { + event.preventDefault(); + loadComic(totalComics); + } + break; + } + } + // Initialize on page load async function init() { // Only run on comic pages @@ -245,6 +286,7 @@ // Get current comic number const currentNumber = parseInt(document.querySelector('.comic-container').dataset.comicNumber || 0); + currentComicNumber = currentNumber; if (currentNumber && totalComics) { // Get the formatted date from the DOM (already rendered by server) @@ -261,6 +303,9 @@ } }); + // Handle keyboard navigation + document.addEventListener('keydown', handleKeyboardNavigation); + // Set initial state history.replaceState({ comicId: currentNumber }, '', window.location.pathname); }