/** * Feed list component for PodTUI * Scrollable list of feeds with keyboard navigation and mouse support */ import { createSignal, For, Show } from "solid-js" import { FeedItem } from "./FeedItem" import { useFeedStore } from "../stores/feed" import { FeedVisibility, FeedSortField } from "../types/feed" import type { Feed } from "../types/feed" interface FeedListProps { focused?: boolean compact?: boolean showEpisodeCount?: boolean showLastUpdated?: boolean onSelectFeed?: (feed: Feed) => void onOpenFeed?: (feed: Feed) => void } export function FeedList(props: FeedListProps) { const feedStore = useFeedStore() const [selectedIndex, setSelectedIndex] = createSignal(0) const filteredFeeds = () => feedStore.getFilteredFeeds() const handleKeyPress = (key: { name: string }) => { const feeds = filteredFeeds() if (key.name === "up" || key.name === "k") { setSelectedIndex((i) => Math.max(0, i - 1)) } else if (key.name === "down" || key.name === "j") { setSelectedIndex((i) => Math.min(feeds.length - 1, i + 1)) } else if (key.name === "return" || key.name === "enter") { const feed = feeds[selectedIndex()] if (feed && props.onOpenFeed) { props.onOpenFeed(feed) } } else if (key.name === "home" || key.name === "g") { setSelectedIndex(0) } else if (key.name === "end") { setSelectedIndex(feeds.length - 1) } else if (key.name === "pageup") { setSelectedIndex((i) => Math.max(0, i - 5)) } else if (key.name === "pagedown") { setSelectedIndex((i) => Math.min(feeds.length - 1, i + 5)) } else if (key.name === "p") { // Toggle pin on selected feed const feed = feeds[selectedIndex()] if (feed) { feedStore.togglePinned(feed.id) } } else if (key.name === "f") { // Cycle visibility filter cycleVisibilityFilter() } else if (key.name === "s") { // Cycle sort cycleSortField() } // Notify selection change const selectedFeed = feeds[selectedIndex()] if (selectedFeed && props.onSelectFeed) { props.onSelectFeed(selectedFeed) } } const cycleVisibilityFilter = () => { const current = feedStore.filter().visibility let next: FeedVisibility | "all" if (current === "all") next = FeedVisibility.PUBLIC else if (current === FeedVisibility.PUBLIC) next = FeedVisibility.PRIVATE else next = "all" feedStore.setFilter({ ...feedStore.filter(), visibility: next }) } const cycleSortField = () => { const sortOptions: FeedSortField[] = [ FeedSortField.UPDATED, FeedSortField.TITLE, FeedSortField.EPISODE_COUNT, FeedSortField.LATEST_EPISODE, ] const current = feedStore.filter().sortBy as FeedSortField const idx = sortOptions.indexOf(current) const next = sortOptions[(idx + 1) % sortOptions.length] feedStore.setFilter({ ...feedStore.filter(), sortBy: next }) } const visibilityLabel = () => { const vis = feedStore.filter().visibility if (vis === "all") return "All" if (vis === "public") return "Public" return "Private" } const sortLabel = () => { const sort = feedStore.filter().sortBy switch (sort) { case "title": return "Title" case "episodeCount": return "Episodes" case "latestEpisode": return "Latest" default: return "Updated" } } const handleFeedClick = (feed: Feed, index: number) => { setSelectedIndex(index) if (props.onSelectFeed) { props.onSelectFeed(feed) } } const handleFeedDoubleClick = (feed: Feed) => { if (props.onOpenFeed) { props.onOpenFeed(feed) } } return ( {/* Header with filter controls */} My Feeds ({filteredFeeds().length} feeds) [f] {visibilityLabel()} [s] {sortLabel()} {/* Feed list in scrollbox */} 0} fallback={ No feeds found. Add podcasts from the Discover or Search tabs. } > {(feed, index) => ( handleFeedClick(feed, index())}> )} {/* Navigation help */} j/k navigate | Enter open | p pin | f filter | s sort | Click to select ) }