/** * Feed detail view component for PodTUI * Shows podcast info and episode list */ import { createSignal, For, Show } from "solid-js" import type { Feed } from "../types/feed" import type { Episode } from "../types/episode" import { format } from "date-fns" interface FeedDetailProps { feed: Feed focused?: boolean onBack?: () => void onPlayEpisode?: (episode: Episode) => void } export function FeedDetail(props: FeedDetailProps) { const [selectedIndex, setSelectedIndex] = createSignal(0) const [showInfo, setShowInfo] = createSignal(true) const episodes = () => { // Sort episodes by publication date (newest first) return [...props.feed.episodes].sort( (a, b) => b.pubDate.getTime() - a.pubDate.getTime() ) } const formatDuration = (seconds: number): string => { const mins = Math.floor(seconds / 60) const hrs = Math.floor(mins / 60) if (hrs > 0) { return `${hrs}h ${mins % 60}m` } return `${mins}m` } const formatDate = (date: Date): string => { return format(date, "MMM d, yyyy") } const handleKeyPress = (key: { name: string }) => { const eps = episodes() if (key.name === "escape" && props.onBack) { props.onBack() return } if (key.name === "i") { setShowInfo((v) => !v) return } 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(eps.length - 1, i + 1)) } else if (key.name === "return" || key.name === "enter") { const episode = eps[selectedIndex()] if (episode && props.onPlayEpisode) { props.onPlayEpisode(episode) } } else if (key.name === "home" || key.name === "g") { setSelectedIndex(0) } else if (key.name === "end") { setSelectedIndex(eps.length - 1) } else if (key.name === "pageup") { setSelectedIndex((i) => Math.max(0, i - 10)) } else if (key.name === "pagedown") { setSelectedIndex((i) => Math.min(eps.length - 1, i + 10)) } } return ( {/* Header with back button */} [Esc] Back setShowInfo((v) => !v)}> [i] {showInfo() ? "Hide" : "Show"} Info {/* Podcast info section */} {props.feed.customName || props.feed.podcast.title} {props.feed.podcast.author && ( by {props.feed.podcast.author} )} {props.feed.podcast.description?.slice(0, 200)} {(props.feed.podcast.description?.length || 0) > 200 ? "..." : ""} Episodes: {props.feed.episodes.length} Updated: {formatDate(props.feed.lastUpdated)} {props.feed.visibility === "public" ? "[Public]" : "[Private]"} {props.feed.isPinned && ( [Pinned] )} {/* Episodes header */} Episodes ({episodes().length} total) {/* Episode list */} {(episode, index) => ( { setSelectedIndex(index()) if (props.onPlayEpisode) { props.onPlayEpisode(episode) } }} > {index() === selectedIndex() ? ">" : " "} {episode.episodeNumber ? `#${episode.episodeNumber} - ` : ""} {episode.title} {formatDate(episode.pubDate)} {formatDuration(episode.duration)} )} {/* Help text */} j/k to navigate, Enter to play, i to toggle info, Esc to go back ) }