diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-14 08:58:38 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-14 08:58:38 -0800 |
| commit | e3c379d069ffa9661561d25cdbf2f5894a2f8ee8 (patch) | |
| tree | 24d0e9f5610dd9c8f873c5b78e6bc1c88d32840a /frontend/src/components/FeedList.tsx | |
| parent | 4b06155fbde91a1bef6361ef36efb28789861928 (diff) | |
| download | neko-e3c379d069ffa9661561d25cdbf2f5894a2f8ee8.tar.gz neko-e3c379d069ffa9661561d25cdbf2f5894a2f8ee8.tar.bz2 neko-e3c379d069ffa9661561d25cdbf2f5894a2f8ee8.zip | |
Refactor: project structure, implement dependency injection, and align v2 UI with v1
Diffstat (limited to 'frontend/src/components/FeedList.tsx')
| -rw-r--r-- | frontend/src/components/FeedList.tsx | 246 |
1 files changed, 138 insertions, 108 deletions
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx index 56c96cd..497baf8 100644 --- a/frontend/src/components/FeedList.tsx +++ b/frontend/src/components/FeedList.tsx @@ -3,121 +3,151 @@ import { Link, useNavigate, useSearchParams, useLocation, useParams } from 'reac import type { Feed, Category } from '../types'; import './FeedList.css'; -export default function FeedList({ theme, setTheme }: { theme: string, setTheme: (t: string) => void }) { - const [feeds, setFeeds] = useState<Feed[]>([]); - const [tags, setTags] = useState<Category[]>([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [feedsExpanded, setFeedsExpanded] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - const location = useLocation(); - const { feedId, tagName } = useParams(); +export default function FeedList({ + theme, + setTheme, +}: { + theme: string; + setTheme: (t: string) => void; +}) { + const [feeds, setFeeds] = useState<Feed[]>([]); + const [tags, setTags] = useState<Category[]>([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [feedsExpanded, setFeedsExpanded] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const location = useLocation(); + const { feedId, tagName } = useParams(); - const currentFilter = searchParams.get('filter') || (location.pathname === '/' && !feedId && !tagName ? 'unread' : ''); + const currentFilter = + searchParams.get('filter') || + (location.pathname === '/' && !feedId && !tagName ? 'unread' : ''); - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - if (searchQuery.trim()) { - navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`); - } - }; + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (searchQuery.trim()) { + navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`); + } + }; - const toggleFeeds = () => { - setFeedsExpanded(!feedsExpanded); - }; + const toggleFeeds = () => { + setFeedsExpanded(!feedsExpanded); + }; - useEffect(() => { - Promise.all([ - fetch('/api/feed/').then(res => { - if (!res.ok) throw new Error('Failed to fetch feeds'); - return res.json(); - }), - fetch('/api/tag').then(res => { - if (!res.ok) throw new Error('Failed to fetch tags'); - return res.json(); - }) - ]) - .then(([feedsData, tagsData]) => { - setFeeds(feedsData); - setTags(tagsData); - setLoading(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }, []); + useEffect(() => { + Promise.all([ + fetch('/api/feed/').then((res) => { + if (!res.ok) throw new Error('Failed to fetch feeds'); + return res.json(); + }), + fetch('/api/tag').then((res) => { + if (!res.ok) throw new Error('Failed to fetch tags'); + return res.json(); + }), + ]) + .then(([feedsData, tagsData]) => { + setFeeds(feedsData); + setTags(tagsData); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, []); - if (loading) return <div className="feed-list-loading">Loading feeds...</div>; - if (error) return <div className="feed-list-error">Error: {error}</div>; + if (loading) return <div className="feed-list-loading">Loading feeds...</div>; + if (error) return <div className="feed-list-error">Error: {error}</div>; - return ( - <div className="feed-list"> - <div className="search-section"> - <form onSubmit={handleSearch} className="search-form"> - <input - type="search" - placeholder="Search items..." - value={searchQuery} - onChange={(e) => setSearchQuery(e.target.value)} - className="search-input" - /> - </form> - </div> - <div className="filter-section"> - <ul className="filter-list"> - <li><Link to="/?filter=unread" className={currentFilter === 'unread' ? 'active' : ''}>Unread</Link></li> - <li><Link to="/?filter=all" className={currentFilter === 'all' ? 'active' : ''}>All</Link></li> - <li><Link to="/?filter=starred" className={currentFilter === 'starred' ? 'active' : ''}>Starred</Link></li> - </ul> - </div> - <div className="feed-section"> - <h2 onClick={toggleFeeds} className="feed-section-header"> - <span className="toggle-indicator">{feedsExpanded ? '▼' : '▶'}</span> Feeds - </h2> - {feedsExpanded && ( - feeds.length === 0 ? ( - <p>No feeds found.</p> - ) : ( - <ul className="feed-list-items"> - {feeds.map((feed) => ( - <li key={feed._id} className="sidebar-feed-item"> - <Link to={`/feed/${feed._id}`} className={`feed-title ${feedId === String(feed._id) ? 'active' : ''}`}> - {feed.title || feed.url} - </Link> - {feed.category && <span className="feed-category">{feed.category}</span>} - </li> - ))} - </ul> - ) - )} - </div> + return ( + <div className="feed-list"> + <div className="search-section"> + <form onSubmit={handleSearch} className="search-form"> + <input + type="search" + placeholder="Search items..." + value={searchQuery} + onChange={(e) => setSearchQuery(e.target.value)} + className="search-input" + /> + </form> + </div> + <div className="filter-section"> + <ul className="filter-list"> + <li> + <Link to="/?filter=unread" className={currentFilter === 'unread' ? 'active' : ''}> + Unread + </Link> + </li> + <li> + <Link to="/?filter=all" className={currentFilter === 'all' ? 'active' : ''}> + All + </Link> + </li> + <li> + <Link to="/?filter=starred" className={currentFilter === 'starred' ? 'active' : ''}> + Starred + </Link> + </li> + </ul> + </div> + <div className="feed-section"> + <h2 onClick={toggleFeeds} className="feed-section-header"> + <span className="toggle-indicator">{feedsExpanded ? '▼' : '▶'}</span> Feeds + </h2> + {feedsExpanded && + (feeds.length === 0 ? ( + <p>No feeds found.</p> + ) : ( + <ul className="feed-list-items"> + {feeds.map((feed) => ( + <li key={feed._id} className="sidebar-feed-item"> + <Link + to={`/feed/${feed._id}`} + className={`feed-title ${feedId === String(feed._id) ? 'active' : ''}`} + > + {feed.title || feed.url} + </Link> + {feed.category && <span className="feed-category">{feed.category}</span>} + </li> + ))} + </ul> + ))} + </div> - {tags && tags.length > 0 && ( - <div className="tag-section"> - <h2>Tags</h2> - <ul className="tag-list-items"> - {tags.map((tag) => ( - <li key={tag.title} className="tag-item"> - <Link to={`/tag/${encodeURIComponent(tag.title)}`} className={`tag-link ${tagName === tag.title ? 'active' : ''}`}> - {tag.title} - </Link> - </li> - ))} - </ul> - </div> - )} + {tags && tags.length > 0 && ( + <div className="tag-section"> + <h2>Tags</h2> + <ul className="tag-list-items"> + {tags.map((tag) => ( + <li key={tag.title} className="tag-item"> + <Link + to={`/tag/${encodeURIComponent(tag.title)}`} + className={`tag-link ${tagName === tag.title ? 'active' : ''}`} + > + {tag.title} + </Link> + </li> + ))} + </ul> + </div> + )} - <div className="theme-section"> - <h2>Themes</h2> - <div className="theme-selector"> - <button onClick={() => setTheme('light')} className={theme === 'light' ? 'active' : ''}>light</button> - <button onClick={() => setTheme('dark')} className={theme === 'dark' ? 'active' : ''}>dark</button> - <button onClick={() => setTheme('black')} className={theme === 'black' ? 'active' : ''}>black</button> - </div> - </div> + <div className="theme-section"> + <div className="theme-selector"> + <button onClick={() => setTheme('light')} className={theme === 'light' ? 'active' : ''}> + light + </button> + <button onClick={() => setTheme('dark')} className={theme === 'dark' ? 'active' : ''}> + dark + </button> + <button onClick={() => setTheme('black')} className={theme === 'black' ? 'active' : ''}> + black + </button> </div> - ); + </div> + </div> + ); } |
