diff --git a/src/components/ComicCard.astro b/src/components/ComicCard.astro index 37687b2..f7923b0 100644 --- a/src/components/ComicCard.astro +++ b/src/components/ComicCard.astro @@ -20,6 +20,8 @@ const isNSFW = comic.md_comics.content_rating === 'erotica';
@@ -50,7 +52,7 @@ const isNSFW = comic.md_comics.content_rating === 'erotica';
-

+

{comic.md_comics.title}

diff --git a/src/config/backgrounds.ts b/src/config/backgrounds.ts index 329fce9..4179bc8 100644 --- a/src/config/backgrounds.ts +++ b/src/config/backgrounds.ts @@ -75,3 +75,10 @@ export function getRandomBackground(): BackgroundImage { } return backgrounds[Math.floor(Math.random() * backgrounds.length)]; } + +export function preloadBackgrounds() { + backgrounds.forEach(bg => { + const img = new Image(); + img.src = bg.url; + }); +} diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 7c27522..7ce0919 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -147,11 +147,12 @@ const isCreditsPage = Astro.url.pathname === '/credits'; } }; - // Initialize theme - const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null; - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light'); - updateThemeToggle(initialTheme); + // Add theme initialization to prevent flash of wrong theme + const themeScript = ` + const theme = localStorage.getItem('theme') ?? + (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + document.documentElement.setAttribute('data-theme', theme); + `; // Event listeners settingsButton?.addEventListener('click', () => { diff --git a/src/lib/api.ts b/src/lib/api.ts index d566cbe..3d482f1 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -69,30 +69,43 @@ export function getGenreNames(genreIds: number[], genres: Genre[]): string[] { .filter(name => name !== ''); } +export class APIError extends Error { + constructor( + message: string, + public statusCode?: number, + public endpoint?: string + ) { + super(message); + this.name = 'APIError'; + } +} + export async function fetchComics( language: string = 'en', contentType: string = 'sfw', comicType: ComicType = 'all', page: number = 1 ): Promise { + const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}${comicType === 'all' ? '' : `&type=${comicType}`}`; + try { - const typeParam = comicType === 'all' ? '' : `&type=${comicType}`; - const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}${typeParam}`; - console.log('Fetching comics from:', apiUrl); const response = await fetchWithRetry(apiUrl); const data = await response.json(); if (!Array.isArray(data)) { - console.error('Unexpected API response format:', data); - return []; + throw new APIError('Invalid API response format', undefined, apiUrl); } return data; } catch (error) { - console.error('Error fetching comics:', error); - return []; + if (error instanceof APIError) throw error; + throw new APIError( + 'Failed to fetch comics', + error instanceof Response ? error.status : undefined, + apiUrl + ); } } diff --git a/src/lib/db.ts b/src/lib/db.ts index 84a8deb..ffcc509 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -3,6 +3,8 @@ import type { FeedMapping } from './types'; const REDIS_URL = import.meta.env.REDIS_URL || process.env.REDIS_URL; let redis: Redis | null = null; +let redisRetryCount = 0; +const MAX_RETRIES = 3; try { console.log('Environment check:', { @@ -23,11 +25,14 @@ try { console.log(`Retry attempt ${times} with delay ${delay}ms`); return delay; }, - maxRetriesPerRequest: 3, + maxRetriesPerRequest: 2, enableReadyCheck: true, - reconnectOnError(err) { - console.log('Reconnect on error:', err.message); - return true; + reconnectOnError: (err) => { + const targetError = 'READONLY'; + if (err.message.includes(targetError)) { + return true; + } + return false; } }); @@ -55,6 +60,38 @@ try { } } +async function getRedisClient(): Promise { + if (redis?.status === 'ready') return redis; + + if (redisRetryCount >= MAX_RETRIES) { + throw new Error('Failed to connect to Redis after multiple attempts'); + } + + try { + redis = new Redis(REDIS_URL, { + retryStrategy(times) { + const delay = Math.min(times * 50, 2000); + console.log(`Retry attempt ${times} with delay ${delay}ms`); + return delay; + }, + maxRetriesPerRequest: 2, + enableReadyCheck: true, + reconnectOnError: (err) => { + const targetError = 'READONLY'; + if (err.message.includes(targetError)) { + return true; + } + return false; + } + }); + redisRetryCount = 0; + return redis; + } catch (error) { + redisRetryCount++; + throw error; + } +} + export async function storeFeedMapping(feedId: string, slugs: string[], lang: string): Promise { if (!redis) { console.error('Redis client not initialized'); diff --git a/src/lib/types.ts b/src/lib/types.ts index 69b3f03..2970743 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -6,11 +6,9 @@ export interface Comic { title: string; slug: string; genres: number[]; - md_covers: { - b2key: string; - }[]; - content_rating: string; - comic_type: string; + md_covers: ComicCover[]; + content_rating: 'safe' | 'suggestive' | 'erotica'; + comic_type: ComicType; }; } @@ -24,4 +22,10 @@ export interface FeedMapping { lang: string; } -export type ComicType = 'manga' | 'manhwa' | 'manhua' | 'all'; \ No newline at end of file +export type ComicType = 'manga' | 'manhwa' | 'manhua' | 'all'; + +export interface ComicCover { + b2key: string; + width?: number; + height?: number; +} \ No newline at end of file