Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SearchResults from './components/SearchResults'
import ComicDisplay from './components/ComicDisplay'
import NavigationButtons from './components/NavigationButtons'
import TranscriptPanel from './components/TranscriptPanel'
import FavoritesPanel from './components/FavoritesPanel'
import DarkModeToggle from './components/DarkModeToggle'
import SettingsModal from './components/SettingsModal'
import Blog from './components/Blog'
Expand Down Expand Up @@ -147,6 +148,16 @@ function ComicsView({ useLocalImages, useArchivedUrls }) {
const [backgroundTotalYears, setBackgroundTotalYears] = useState(0)
const [isSettingsOpen, setIsSettingsOpen] = useState(false)

const [favorites, setFavorites] = useState(() => {

if (typeof window !== 'undefined') {
const saved = localStorage.getItem('favorites')
return saved ? JSON.parse(saved) : []
}
return []

})

const baseUrl = import.meta.env.BASE_URL

// Helper function to load a year's data (with caching)
Expand Down Expand Up @@ -813,7 +824,7 @@ function ComicsView({ useLocalImages, useArchivedUrls }) {

return (
<>
{currentDate && currentComic ? (
{currentDate ? (
<div className="flex flex-col lg:flex-row gap-6 w-full">
{/* Left column - Comic (70%) */}
<div className="w-full lg:flex-[7] min-w-0">
Expand All @@ -824,6 +835,8 @@ function ComicsView({ useLocalImages, useArchivedUrls }) {
comicsData={comicsData}
comicsIndex={comicsIndex}
useLocalImages={useLocalImages}
favorites={favorites}
setFavorites={setFavorites}
useArchivedUrls={useArchivedUrls}
/>
</div>
Expand Down Expand Up @@ -885,20 +898,23 @@ function ComicsView({ useLocalImages, useArchivedUrls }) {
)}
</div>
) : (
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-4">
<TranscriptPanel
date={currentDate}
comic={currentComic}
/>
</div>
<>
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-4">
<TranscriptPanel
date={currentDate}
comic={currentComic}
/>
</div>
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-4">
<FavoritesPanel
favorites={favorites}
setFavorites={setFavorites}
/>
</div>
</>
)}
</div>
</div>
) : currentDate ? (
<div className="text-center py-12">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 dark:border-blue-400 mb-4"></div>
<p className="text-gray-600 dark:text-gray-400">Loading comic data...</p>
</div>
) : null}

{/* Background Loading Status */}
Expand Down
52 changes: 38 additions & 14 deletions src/components/ComicDisplay.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useCallback, useState } from 'react'

function ComicDisplay({ date, comic, comicsData, comicsIndex, useLocalImages, useArchivedUrls}) {
function ComicDisplay({ date, comic, comicsData, comicsIndex, useLocalImages, useArchivedUrls, favorites,setFavorites }) {
const [showTranscript, setShowTranscript] = useState(false)
const [imageError, setImageError] = useState(false)
const [isArchiveOrgError, setIsArchiveOrgError] = useState(false)
Expand Down Expand Up @@ -35,6 +35,7 @@ function ComicDisplay({ date, comic, comicsData, comicsIndex, useLocalImages, us
}

// When local images are disabled, use archive.org URL
// return comicData.originalimageurl || ''
return comicData.originalimageurl || ''
}, [useLocalImages,useArchivedUrls])

Expand Down Expand Up @@ -87,19 +88,42 @@ function ComicDisplay({ date, comic, comicsData, comicsIndex, useLocalImages, us

return (
<section aria-labelledby={`comic-${date}`}>
<div className="mb-4 pb-4 border-b border-gray-200 dark:border-gray-700">
<h2 id={`comic-${date}`} className="text-xl md:text-2xl font-bold text-gray-800 dark:text-gray-100 mb-2">
{h2Content}
</h2>
<div className="flex items-center gap-2 text-xs md:text-sm text-gray-600 dark:text-gray-400">
<svg className="w-3 h-3 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<time dateTime={date} className="font-medium">
{formatDateString(date)}
</time>
</div>
</div>
<div className="mb-4 pb-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-start justify-between gap-3 mb-2">
<h2 id={`comic-${date}`} className="text-xl md:text-2xl font-bold text-gray-800 dark:text-gray-100 min-w-0">
{h2Content}
</h2>

<button
type="button"
onClick={() => {
const updatedFavorites = favorites.includes(date)
? favorites.filter((favoriteDate) => favoriteDate !== date)
: [...favorites, date]

setFavorites(updatedFavorites.sort())
console.log('update')
localStorage.setItem('favorites', JSON.stringify(updatedFavorites))
}}
className="flex-shrink-0 p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label={favorites.includes(date) ? 'Remove from favorites' : 'Add to favorites'}
title={favorites.includes(date) ? 'Remove from favorites' : 'Add to favorites'}
>
<svg className="w-5 h-5 text-blue-600 dark:text-blue-400" fill={favorites.includes(date) ? "currentColor": "none"} stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="butt" strokeLinejoin="miter" strokeWidth={2} d="M12 3l2.9 6.26L22 9.27l-5 4.87L18.8 21 12 17.77 5.2 21 7 14.14 2 9.27l7.1-.01L12 3z"/>
</svg>
</button>
</div>

<div className="flex items-center gap-2 text-xs md:text-sm text-gray-600 dark:text-gray-400">
<svg className="w-3 h-3 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<time dateTime={date} className="font-medium">
{formatDateString(date)}
</time>
</div>
</div>

<div>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-2 md:p-4 border border-gray-200 dark:border-gray-700">
Expand Down
71 changes: 71 additions & 0 deletions src/components/FavoritesPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'

function FavoritesPanel({ favorites, setFavorites }) {
const [showFavorites, setShowFavorites] = useState(false)
const navigate = useNavigate()
const { date: currentDate } = useParams()

return (
<div role="document" aria-labelledby={`favorites`} className="mb-6">
<button
onClick={() => setShowFavorites(!showFavorites)}
className="flex items-center justify-between w-full gap-2 mb-3 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors group border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800"
aria-expanded={showFavorites}
aria-controls={`favorites-panel`}
>
<div className="flex items-center gap-2">
<svg
className={`w-4 h-4 text-blue-600 dark:text-blue-400 transition-transform ${showFavorites ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
<svg
className="w-5 h-5 text-blue-600 dark:text-blue-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="butt" strokeLinejoin="miter" strokeWidth={2} d="M12 3l2.9 6.26L22 9.27l-5 4.87L18.8 21 12 17.77 5.2 21 7 14.14 2 9.27l7.1-.01L12 3z" />
</svg>
<h3 id={`favorites`} className="text-sm md:text-base font-bold text-gray-800 dark:text-gray-100">
Favorites
</h3>
</div>
<span className="text-xs text-gray-500 dark:text-gray-400 group-hover:text-gray-700 dark:group-hover:text-gray-300">
{showFavorites ? 'Hide' : 'Show'}
</span>
</button>

{showFavorites && (
<div
id={`favorites-panel`}
className="text-white bg-gray-50 dark:bg-gray-900 rounded-lg p-4 border border-gray-200 dark:border-gray-700 animate-fadeIn max-h-96 overflow-y-auto"
>
{favorites.length > 0 ? (
favorites.map((date) => (
<button
key={date}
onClick={() => navigate(`/comic/${date}`)}
className={`inline-block px-3 py-2 mr-2 mb-2 rounded-lg transition-colors text-sm ${
date === currentDate
? 'bg-blue-800 scale-105'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
{date}
</button>
))
) : (
<p>Click the star to favorite comics.</p>
)}
</div>
)}
</div>
)
}

export default FavoritesPanel