diff --git a/README.md b/README.md index 7919da1..0602363 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # Rufeed Rufeed is a lightweight desktop feed reader built with Tauri and React. + +## Screenshot + +![Rufeed app screenshot](./public/screenshot.png) diff --git a/public/screenshot.png b/public/screenshot.png new file mode 100644 index 0000000..23ec79c Binary files /dev/null and b/public/screenshot.png differ diff --git a/src-tauri/src/commands/feed.rs b/src-tauri/src/commands/feed.rs index 1be9367..b1e2194 100644 --- a/src-tauri/src/commands/feed.rs +++ b/src-tauri/src/commands/feed.rs @@ -1,7 +1,6 @@ use cached::proc_macro::cached; use feed_rs::parser; use reqwest::header::{ACCEPT, ACCEPT_ENCODING}; -use tauri::utils::config::parse; use crate::client::CLIENT; use crate::config::feed_config::Feed; @@ -65,11 +64,15 @@ pub async fn add_feed_direct(feed_url: &str) -> Result { let feed = parser::parse(xml.as_bytes())?; let title = feed.title.map(|t| t.content).unwrap_or("no title".into()); - let icon = feed.icon.map(|i| i.uri).unwrap_or("defult.png".into()); let url = url::Url::parse(feed_url) .unwrap() .origin() .ascii_serialization(); + + let icon = feed + .icon + .map(|i| i.uri) + .unwrap_or(format!("{url}/favicon.ico")); Feed::add(&title, &url, feed_url, &icon) } diff --git a/src-tauri/src/parser.rs b/src-tauri/src/parser.rs index 870b6af..1260e8b 100644 --- a/src-tauri/src/parser.rs +++ b/src-tauri/src/parser.rs @@ -21,19 +21,19 @@ impl FeedItem { title: entry .title .map(|t| t.content) - .ok_or(Error::MissingField("title".into()))?, + .unwrap_or("no_title".to_string()), published: entry .published .or(entry.updated) .map(|d| d.to_rfc3339()) - .ok_or(Error::MissingField("published".into()))?, + .unwrap_or("no_published".to_string()), url: entry .links .iter() .find(|l| l.rel.as_deref() == Some("alternate")) .or_else(|| entry.links.first()) .map(|l| l.href.clone()) - .ok_or(Error::MissingField("url".into()))?, + .unwrap_or("no_url".to_string()), }; items.push(item); } @@ -79,18 +79,18 @@ impl FeedEntry { title: entry .title .map(|t| t.content) - .ok_or(Error::MissingField("title".into()))?, + .unwrap_or("no_title".to_string()), url, published: entry .published .or(entry.updated) .map(|d| d.to_rfc3339()) - .ok_or(Error::MissingField("published".into()))?, + .unwrap_or("no_published".to_string()), updated: entry .updated .or(entry.published) .map(|d| d.to_rfc3339()) - .ok_or(Error::MissingField("updated".into()))?, + .unwrap_or("no_updated".to_string()), summary, content, authors: entry diff --git a/src/App.tsx b/src/App.tsx index 9c308d3..a04b6fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -59,8 +59,7 @@ export default function App() { ); const handleRemoveFeed = useCallback( - async (feed: Feed, e: React.MouseEvent) => { - e.stopPropagation(); + async (feed: Feed) => { try { await removeFeed(feed); } catch (err) { diff --git a/src/components/sidebar/FeedListItem.tsx b/src/components/sidebar/FeedListItem.tsx index eaf8708..6000070 100644 --- a/src/components/sidebar/FeedListItem.tsx +++ b/src/components/sidebar/FeedListItem.tsx @@ -1,12 +1,12 @@ -import { memo } from "react"; -import { Rss, Trash2 } from "lucide-react"; +import { memo, useCallback, useState, type MouseEvent } from "react"; +import { Check, Rss, Trash2, X } from "lucide-react"; import { Feed } from "../../bindings"; interface FeedListItemProps { feed: Feed; isSelected: boolean; onSelect: (feed: Feed) => void; - onRemove: (feed: Feed, e: React.MouseEvent) => void; + onRemove: (feed: Feed) => void; } export const FeedListItem = memo(function FeedListItem({ @@ -15,6 +15,27 @@ export const FeedListItem = memo(function FeedListItem({ onSelect, onRemove, }: FeedListItemProps) { + const [isConfirmingDelete, setIsConfirmingDelete] = useState(false); + + const handleToggleDeleteConfirm = useCallback((e: MouseEvent) => { + e.stopPropagation(); + setIsConfirmingDelete((value) => !value); + }, []); + + const handleCancelDelete = useCallback((e: MouseEvent) => { + e.stopPropagation(); + setIsConfirmingDelete(false); + }, []); + + const handleConfirmDelete = useCallback( + (e: MouseEvent) => { + e.stopPropagation(); + onRemove(feed); + setIsConfirmingDelete(false); + }, + [feed, onRemove] + ); + return ( ); }); diff --git a/src/components/sidebar/SidebarContent.tsx b/src/components/sidebar/SidebarContent.tsx index 3f521fc..0cdc18b 100644 --- a/src/components/sidebar/SidebarContent.tsx +++ b/src/components/sidebar/SidebarContent.tsx @@ -19,7 +19,7 @@ interface SidebarContentProps { loading: boolean; selectedFeedId?: string; onSelectFeed: (feed: Feed) => void; - onRemoveFeed: (feed: Feed, e: React.MouseEvent) => void; + onRemoveFeed: (feed: Feed) => void; onAddFeed: (url: string) => Promise; onRefresh: () => void; onHideSidebar?: () => void;