diff --git a/src/components/Selectable.tsx b/src/components/Selectable.tsx
index 77436e7..3a590e9 100644
--- a/src/components/Selectable.tsx
+++ b/src/components/Selectable.tsx
@@ -6,14 +6,12 @@ export function SelectableBox({
selected,
children,
...props
-}: {
- selected: () => boolean;
- children: JSXElement;
-} & BoxOptions) {
+}: { selected: () => boolean; children: JSXElement } & BoxOptions) {
const { theme } = useTheme();
return (
Categories:
-
-
- {(category) => {
- const isSelected = () =>
- discoverStore.selectedCategory() === category.id;
+
+
+ {(category) => {
+ const isSelected = () =>
+ discoverStore.selectedCategory() === category.id;
- return (
- handleCategorySelect(category.id)}
- >
-
- {category.icon} {category.name}
-
-
- );
- }}
-
-
+ return (
+ handleCategorySelect(category.id)}
+ >
+
+ {category.icon} {category.name}
+
+
+ );
+ }}
+
+
props.selected}
flexDirection="column"
padding={1}
- backgroundColor={props.selected ? theme.backgroundElement : undefined}
onMouseDown={props.onSelect}
>
- {/* Title Row */}
-
- {props.podcast.title}
-
+ props.selected}>
+ {props.podcast.title}
+
- [+]
+ [+]
{/* Author */}
- by {props.podcast.author}
+ props.selected}
+ fg={theme.textMuted}
+ >
+ by {props.podcast.author}
+
{/* Description */}
-
+ props.selected}
+ fg={theme.text}
+ >
{props.podcast.description!.length > 80
? props.podcast.description!.slice(0, 80) + "..."
: props.podcast.description}
-
+
- {/* Categories and Subscribe Button */}
-
-
- 0}>
-
- {(cat) => [{cat}]}
-
-
-
-
-
-
-
- {props.podcast.isSubscribed ? "[Unsubscribe]" : "[Subscribe]"}
-
-
+ />**/}
+
+ 0}>
+
+ {(cat) => [{cat}]}
+
-
+
+
+
+
+ {props.podcast.isSubscribed ? "[Unsubscribe]" : "[Subscribe]"}
+
+
+
+
);
}
diff --git a/src/pages/Feed/FeedDetail.tsx b/src/pages/Feed/FeedDetail.tsx
index 83edd53..5cb801d 100644
--- a/src/pages/Feed/FeedDetail.tsx
+++ b/src/pages/Feed/FeedDetail.tsx
@@ -9,6 +9,7 @@ import type { Feed } from "@/types/feed";
import type { Episode } from "@/types/episode";
import { format } from "date-fns";
import { useTheme } from "@/context/ThemeContext";
+import { SelectableBox, SelectableText } from "@/components/Selectable";
interface FeedDetailProps {
feed: Feed;
@@ -139,11 +140,11 @@ export function FeedDetail(props: FeedDetailProps) {
{(episode, index) => (
- index() === selectedIndex()}
flexDirection="column"
gap={0}
padding={1}
- backgroundColor={index() === selectedIndex() ? theme.backgroundElement : undefined}
onMouseDown={() => {
setSelectedIndex(index());
if (props.onPlayEpisode) {
@@ -151,20 +152,24 @@ export function FeedDetail(props: FeedDetailProps) {
}
}}
>
-
-
- {index() === selectedIndex() ? ">" : " "}
-
-
- {episode.episodeNumber ? `#${episode.episodeNumber} - ` : ""}
- {episode.title}
-
-
+ index() === selectedIndex()}
+ fg={theme.primary}
+ >
+ {index() === selectedIndex() ? ">" : " "}
+
+ index() === selectedIndex()}
+ fg={theme.text}
+ >
+ {episode.episodeNumber ? `#${episode.episodeNumber} - ` : ""}
+ {episode.title}
+
{formatDate(episode.pubDate)}
{formatDuration(episode.duration)}
-
+
)}
diff --git a/src/pages/Feed/FeedItem.tsx b/src/pages/Feed/FeedItem.tsx
index f055e8e..d4bced8 100644
--- a/src/pages/Feed/FeedItem.tsx
+++ b/src/pages/Feed/FeedItem.tsx
@@ -6,6 +6,7 @@
import type { Feed, FeedVisibility } from "@/types/feed";
import { format } from "date-fns";
import { useTheme } from "@/context/ThemeContext";
+import { SelectableBox, SelectableText } from "@/components/Selectable";
interface FeedItemProps {
feed: Feed;
@@ -43,70 +44,111 @@ export function FeedItem(props: FeedItemProps) {
if (props.compact) {
// Compact single-line view
return (
- props.isSelected}
flexDirection="row"
gap={1}
- backgroundColor={props.isSelected ? theme.backgroundElement : undefined}
paddingLeft={1}
paddingRight={1}
+ onMouseDown={() => {}}
>
-
+ props.isSelected}
+ fg={theme.primary}
+ >
{props.isSelected ? ">" : " "}
-
- {visibilityIcon()}
-
+
+ props.isSelected}
+ fg={visibilityColor()}
+ >
+ {visibilityIcon()}
+
+ props.isSelected}
+ fg={theme.text}
+ >
{props.feed.customName || props.feed.podcast.title}
-
+
{props.showEpisodeCount && (
- ({episodeCount()})
+ props.isSelected}
+ fg={theme.textMuted}
+ >
+ ({episodeCount()})
+
)}
-
+
);
}
// Full view with details
return (
- props.isSelected}
flexDirection="column"
gap={0}
- border={props.isSelected}
- borderColor={props.isSelected ? theme.primary : undefined}
- backgroundColor={props.isSelected ? theme.primary : undefined}
padding={1}
+ onMouseDown={() => {}}
>
{/* Title row */}
-
+ props.isSelected}
+ fg={theme.primary}
+ >
{props.isSelected ? ">" : " "}
-
- {visibilityIcon()}
- {pinnedIndicator()}
-
+
+ props.isSelected}
+ fg={visibilityColor()}
+ >
+ {visibilityIcon()}
+
+ props.isSelected}
+ fg={theme.warning}
+ >
+ {pinnedIndicator()}
+
+ props.isSelected}
+ fg={theme.text}
+ >
{props.feed.customName || props.feed.podcast.title}
-
+
{props.showEpisodeCount && (
-
+ props.isSelected}
+ fg={theme.textMuted}
+ >
{episodeCount()} episodes ({unplayedCount()} new)
-
+
)}
{props.showLastUpdated && (
-
+ props.isSelected}
+ fg={theme.textMuted}
+ >
Updated: {formatDate(props.feed.lastUpdated)}
-
+
)}
{props.feed.podcast.description && (
-
-
- {props.feed.podcast.description.slice(0, 60)}
- {props.feed.podcast.description.length > 60 ? "..." : ""}
-
-
+ props.isSelected}
+ paddingLeft={4}
+ paddingTop={0}
+ fg={theme.textMuted}
+ >
+ {props.feed.podcast.description.slice(0, 60)}
+ {props.feed.podcast.description.length > 60 ? "..." : ""}
+
)}
-
+
);
}
diff --git a/src/pages/Feed/FeedPage.tsx b/src/pages/Feed/FeedPage.tsx
index 9c40ebe..1b1aee4 100644
--- a/src/pages/Feed/FeedPage.tsx
+++ b/src/pages/Feed/FeedPage.tsx
@@ -10,6 +10,7 @@ import type { Episode } from "@/types/episode";
import type { Feed } from "@/types/feed";
import { useTheme } from "@/context/ThemeContext";
import { PageProps } from "@/App";
+import { SelectableBox, SelectableText } from "@/components/Selectable";
enum FeedPaneType {
FEED = 1,
@@ -27,6 +28,18 @@ export function FeedPage(props: PageProps) {
return format(date, "MMM d, yyyy");
};
+ const episodesByDate = () => {
+ const groups: Record = {};
+ const sortedEpisodes = allEpisodes();
+
+ for (const episode of sortedEpisodes) {
+ const dateKey = formatDate(new Date(episode.episode.pubDate));
+ groups[dateKey] = episode;
+ }
+
+ return groups;
+ };
+
const formatDuration = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const hrs = Math.floor(mins / 60);
@@ -63,36 +76,43 @@ export function FeedPage(props: PageProps) {
}
>
- {/**TODO: figure out wtf to do here **/}
-
-
- {(item, index) => (
- setSelectedIndex(index())}
- >
-
-
- {index() === selectedIndex() ? ">" : " "}
-
-
- {item.episode.title}
-
+
+ b.localeCompare(a))}>
+ {([date, episode], groupIndex) => (
+ <>
+
+ {date}
-
- {item.feed.podcast.title}
- {formatDate(item.episode.pubDate)}
- {formatDuration(item.episode.duration)}
-
-
+ groupIndex() === selectedIndex()}
+ flexDirection="column"
+ gap={0}
+ paddingLeft={1}
+ paddingRight={1}
+ paddingTop={0}
+ paddingBottom={0}
+ onMouseDown={() => setSelectedIndex(groupIndex())}
+ >
+ groupIndex() === selectedIndex()}>
+ {groupIndex() === selectedIndex() ? ">" : " "}
+
+ groupIndex() === selectedIndex()}
+ fg={theme.text}
+ >
+ {episode.episode.title}
+
+
+ {episode.feed.podcast.title}
+
+ {formatDate(episode.episode.pubDate)}
+
+
+ {formatDuration(episode.episode.duration)}
+
+
+
+ >
)}
diff --git a/src/pages/Search/ResultCard.tsx b/src/pages/Search/ResultCard.tsx
index 5f6f060..15dec65 100644
--- a/src/pages/Search/ResultCard.tsx
+++ b/src/pages/Search/ResultCard.tsx
@@ -2,6 +2,7 @@ import { Show } from "solid-js";
import type { SearchResult } from "@/types/source";
import { SourceBadge } from "./SourceBadge";
import { useTheme } from "@/context/ThemeContext";
+import { SelectableBox, SelectableText } from "@/components/Selectable";
type ResultCardProps = {
result: SearchResult;
@@ -15,12 +16,10 @@ export function ResultCard(props: ResultCardProps) {
const podcast = () => props.result.podcast;
return (
- props.selected}
flexDirection="column"
padding={1}
- border={props.selected}
- borderColor={props.selected ? theme.primary : undefined}
- backgroundColor={props.selected ? theme.backgroundElement : undefined}
onMouseDown={props.onSelect}
>
-
+ props.selected}
+ fg={theme.primary}
+ >
{podcast().title}
-
+
- by {podcast().author}
+ props.selected}
+ fg={theme.textMuted}
+ >
+ by {podcast().author}
+
{(description) => (
-
+ props.selected}
+ fg={theme.text}
+ >
{description().length > 120
? description().slice(0, 120) + "..."
: description()}
-
+
)}
@@ -80,6 +90,6 @@ export function ResultCard(props: ResultCardProps) {
[+] Add to Feeds
-
+
);
}
diff --git a/src/pages/Search/SearchHistory.tsx b/src/pages/Search/SearchHistory.tsx
index 6b3d338..f0ffc49 100644
--- a/src/pages/Search/SearchHistory.tsx
+++ b/src/pages/Search/SearchHistory.tsx
@@ -4,6 +4,7 @@
import { For, Show } from "solid-js"
import { useTheme } from "@/context/ThemeContext"
+import { SelectableBox, SelectableText } from "@/components/Selectable"
type SearchHistoryProps = {
history: string[]
@@ -52,23 +53,31 @@ export function SearchHistory(props: SearchHistoryProps) {
const isSelected = () => index() === props.selectedIndex && props.focused
return (
- handleSearchClick(index(), query)}
>
-
- {">"}
- {query}
-
+
+ {">"}
+
+
+ {query}
+
handleRemoveClick(query)} padding={0}>
[x]
-
+
)
}}
diff --git a/src/pages/Settings/SourceManager.tsx b/src/pages/Settings/SourceManager.tsx
index ea9850b..bfd02db 100644
--- a/src/pages/Settings/SourceManager.tsx
+++ b/src/pages/Settings/SourceManager.tsx
@@ -8,6 +8,7 @@ import { useFeedStore } from "@/stores/feed";
import { useTheme } from "@/context/ThemeContext";
import { SourceType } from "@/types/source";
import type { PodcastSource } from "@/types/source";
+import { SelectableBox, SelectableText } from "@/components/Selectable";
interface SourceManagerProps {
focused?: boolean;
@@ -183,24 +184,20 @@ export function SourceManager(props: SourceManagerProps) {
Sources:
-
-
- {(source, index) => (
- {
- setSelectedIndex(index());
- setFocusArea("list");
- feedStore.toggleSource(source.id);
- }}
- >
+
+
+ {(source, index) => (
+ focusArea() === "list" && index() === selectedIndex()}
+ flexDirection="row"
+ gap={1}
+ padding={0}
+ onMouseDown={() => {
+ setSelectedIndex(index());
+ setFocusArea("list");
+ feedStore.toggleSource(source.id);
+ }}
+ >
"
: " "}
-
- {source.enabled ? "[x]" : "[ ]"}
-
- {getSourceIcon(source)}
- focusArea() === "list" && index() === selectedIndex()}
+ fg={theme.text}
>
{source.name}
-
-
+
+
)}
diff --git a/src/ui/command.tsx b/src/ui/command.tsx
index af8ae6d..1113aac 100644
--- a/src/ui/command.tsx
+++ b/src/ui/command.tsx
@@ -15,6 +15,7 @@ import { useDialog } from "./dialog";
import { useTheme } from "../context/ThemeContext";
import { TextAttributes } from "@opentui/core";
import { emit } from "../utils/event-bus";
+import { SelectableBox, SelectableText } from "@/components/Selectable";
/**
* Command option for the command palette.
@@ -281,37 +282,53 @@ function CommandDialog(props: {
{(option, index) => (
- index() === selectedIndex()}
+ flexDirection="column"
padding={1}
+ onMouseDown={() => {
+ setSelectedIndex(index());
+ const selectedOption = filteredOptions()[index()];
+ if (selectedOption) {
+ selectedOption.onSelect?.(dialog);
+ dialog.clear();
+ }
+ }}
>
-
- index() === selectedIndex()}
+ fg={
+ index() === selectedIndex()
+ ? theme.selectedListItemText
+ : theme.text
+ }
+ attributes={
+ index() === selectedIndex()
+ ? TextAttributes.BOLD
+ : undefined
+ }
+ >
+ {option.title}
+
+
+ index() === selectedIndex()}
+ fg={theme.textMuted}
>
- {option.title}
-
-
- {option.footer}
-
-
+ {option.footer}
+
+
- {option.description}
+ index() === selectedIndex()}
+ fg={theme.textMuted}
+ >
+ {option.description}
+
-
+
)}