/** * Feed detail view component for PodTUI * Shows podcast info and episode list */ import { createSignal, For, Show } from "solid-js"; import { useKeyboard } from "@opentui/solid"; 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)); } }; useKeyboard((key) => { if (!props.focused) return; handleKeyPress(key); }); 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 ); }