import { useEffect, useState } from 'react'; import { Link, useNavigate, useSearchParams, useLocation, useMatch } from 'react-router-dom'; import type { Feed, Category } from '../types'; import './FeedList.css'; import './FeedListVariants.css'; import { apiFetch } from '../utils'; export default function FeedList({ theme, setTheme, setSidebarVisible, isMobile, }: { theme: string; setTheme: (t: string) => void; setSidebarVisible: (visible: boolean) => void; isMobile: boolean; }) { const [feeds, setFeeds] = useState([]); const [tags, setTags] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [feedsExpanded, setFeedsExpanded] = useState(false); const [tagsExpanded, setTagsExpanded] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const location = useLocation(); const feedMatch = useMatch('/feed/:feedId'); const tagMatch = useMatch('/tag/:tagName'); const isRoot = useMatch('/'); const isStreamPage = !!(isRoot || feedMatch || tagMatch); const feedId = feedMatch?.params.feedId; const tagName = tagMatch?.params.tagName; const sidebarVariant = searchParams.get('sidebar') || localStorage.getItem('neko-sidebar-variant') || 'glass'; useEffect(() => { const variant = searchParams.get('sidebar'); if (variant) { localStorage.setItem('neko-sidebar-variant', variant); } }, [searchParams]); const currentFilter = searchParams.get('filter') || (isStreamPage ? 'unread' : ''); const getFilterLink = (filter: string) => { const baseStreamPath = isStreamPage ? location.pathname : '/'; const params = new URLSearchParams(searchParams); params.set('filter', filter); return `${baseStreamPath}?${params.toString()}`; }; const getNavPath = (path: string) => { const params = new URLSearchParams(searchParams); if (!params.has('filter') && currentFilter) { params.set('filter', currentFilter); } const qs = params.toString(); return `${path}${qs ? '?' + qs : ''}`; }; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); if (searchQuery.trim()) { const params = new URLSearchParams(searchParams); params.set('q', searchQuery.trim()); if (currentFilter) params.set('filter', currentFilter); navigate(`/?${params.toString()}`); } }; const toggleFeeds = () => { setFeedsExpanded(!feedsExpanded); }; const toggleTags = () => { setTagsExpanded(!tagsExpanded); }; const handleLinkClick = () => { if (isMobile) { setSidebarVisible(false); } }; useEffect(() => { Promise.all([ apiFetch('/api/feed/').then((res) => { if (!res.ok) throw new Error('Failed to fetch feeds'); return res.json() as Promise; }), apiFetch('/api/tag').then((res) => { if (!res.ok) throw new Error('Failed to fetch tags'); return res.json() as Promise; }), ]) .then(([feedsData, tagsData]) => { setFeeds(feedsData); setTags(tagsData); setLoading(false); }) .catch((err) => { setError(err.message); setLoading(false); }); }, []); if (loading) return
Loading feeds...
; if (error) return
Error: {error}
; const handleLogout = () => { apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/login')); }; return (

setSidebarVisible(false)}> 🐱

setSearchQuery(e.target.value)} className="search-input" />
  • unread
  • all
  • starred

Tags

{tagsExpanded && (
    {tags.map((tag) => (
  • {tag.title}
  • ))}
)}

Feeds

{feedsExpanded && (feeds.length === 0 ? (

No feeds found.

) : (
    {feeds.map((feed) => (
  • {feed.title || feed.url}
  • ))}
))}
  • settings
); }