aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components/FeedList.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/FeedList.tsx')
-rw-r--r--frontend/src/components/FeedList.tsx246
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>
+ );
}