chore: added feed delete
0c6d7cf4
3 file(s) · +65 −4
| 25 | 25 | allReadStatusesWithUnreadQuery, |
|
| 26 | 26 | useEvolu, |
|
| 27 | 27 | reset, |
|
| 28 | + | evolu as evoluInstance, |
|
| 28 | 29 | } from "@/lib/evolu"; |
|
| 30 | + | import * as Evolu from "@evolu/common"; |
|
| 29 | 31 | import { useQuery } from "@evolu/react"; |
|
| 30 | 32 | import { |
|
| 31 | 33 | fetchFeedWithFallback, |
|
| 224 | 226 | ); |
|
| 225 | 227 | }, [filteredPosts, allReadStatusesWithUnread, evolu]); |
|
| 226 | 228 | ||
| 229 | + | // Delete feed (soft delete using isDeleted flag) |
|
| 230 | + | const handleDeleteFeed = React.useCallback(() => { |
|
| 231 | + | if (!selectedFeedId) return; |
|
| 232 | + | ||
| 233 | + | const feedToDelete = allFeeds.find((f) => f.id === selectedFeedId); |
|
| 234 | + | if (!feedToDelete) return; |
|
| 235 | + | ||
| 236 | + | // Soft delete the feed (CRDT-friendly, preserves sync history) |
|
| 237 | + | evoluInstance.update("rssFeed", { |
|
| 238 | + | id: selectedFeedId as any, |
|
| 239 | + | isDeleted: Evolu.sqliteTrue, |
|
| 240 | + | }); |
|
| 241 | + | ||
| 242 | + | // Also soft delete all posts associated with this feed |
|
| 243 | + | const postsToDelete = allPosts.filter((p) => p.feedId === selectedFeedId); |
|
| 244 | + | postsToDelete.forEach((post) => { |
|
| 245 | + | evoluInstance.update("rssPost", { |
|
| 246 | + | id: post.id as any, |
|
| 247 | + | isDeleted: Evolu.sqliteTrue, |
|
| 248 | + | }); |
|
| 249 | + | }); |
|
| 250 | + | ||
| 251 | + | toast.success( |
|
| 252 | + | `Deleted feed "${feedToDelete.title}" and ${postsToDelete.length} post${postsToDelete.length !== 1 ? "s" : ""}`, |
|
| 253 | + | ); |
|
| 254 | + | ||
| 255 | + | // Navigate back to all feeds |
|
| 256 | + | onFeedSelect(null); |
|
| 257 | + | }, [selectedFeedId, allFeeds, allPosts, onFeedSelect]); |
|
| 258 | + | ||
| 227 | 259 | const refreshFeeds = React.useCallback(async () => { |
|
| 228 | 260 | if (allFeeds.length === 0) { |
|
| 229 | 261 | toast.error("No feeds to refresh"); |
|
| 344 | 376 | isPostRead={isPostRead} |
|
| 345 | 377 | onMarkAllAsRead={handleMarkAllAsRead} |
|
| 346 | 378 | onMarkAllAsUnread={handleMarkAllAsUnread} |
|
| 379 | + | onDeleteFeed={handleDeleteFeed} |
|
| 380 | + | selectedFeedId={selectedFeedId} |
|
| 347 | 381 | className="border-0" |
|
| 348 | 382 | /> |
|
| 349 | 383 | </div> |
|
| 381 | 415 | isPostRead={isPostRead} |
|
| 382 | 416 | onMarkAllAsRead={handleMarkAllAsRead} |
|
| 383 | 417 | onMarkAllAsUnread={handleMarkAllAsUnread} |
|
| 418 | + | onDeleteFeed={handleDeleteFeed} |
|
| 419 | + | selectedFeedId={selectedFeedId} |
|
| 384 | 420 | className={`bg-sidebar text-sidebar-foreground hidden md:flex ${hidden ? "w-0 min-w-0 border-0 overflow-hidden" : "w-[320px] overflow-y-auto"}`} |
|
| 385 | 421 | /> |
|
| 386 | 422 | </> |
|
| 1 | - | import { MoreVertical, Check, X } from "lucide-react"; |
|
| 1 | + | import { MoreVertical, Check, X, Trash2 } from "lucide-react"; |
|
| 2 | 2 | import { Button } from "@/components/ui/button"; |
|
| 3 | 3 | import { |
|
| 4 | 4 | DropdownMenu, |
|
| 28 | 28 | isPostRead: (postId: string) => boolean; |
|
| 29 | 29 | onMarkAllAsRead: () => void; |
|
| 30 | 30 | onMarkAllAsUnread: () => void; |
|
| 31 | + | onDeleteFeed?: () => void; |
|
| 32 | + | selectedFeedId?: string | null; |
|
| 31 | 33 | className?: string; |
|
| 32 | 34 | } |
|
| 33 | 35 | ||
| 41 | 43 | isPostRead, |
|
| 42 | 44 | onMarkAllAsRead, |
|
| 43 | 45 | onMarkAllAsUnread, |
|
| 46 | + | onDeleteFeed, |
|
| 47 | + | selectedFeedId, |
|
| 44 | 48 | className = "", |
|
| 45 | 49 | }: PostsListProps) { |
|
| 46 | 50 | return ( |
|
| 69 | 73 | <X className="h-4 w-4 mr-2" /> |
|
| 70 | 74 | Mark all as unread |
|
| 71 | 75 | </DropdownMenuItem> |
|
| 76 | + | {selectedFeedId && onDeleteFeed && ( |
|
| 77 | + | <DropdownMenuItem |
|
| 78 | + | onClick={onDeleteFeed} |
|
| 79 | + | className="text-destructive focus:text-destructive" |
|
| 80 | + | > |
|
| 81 | + | <Trash2 className="h-4 w-4 mr-2" /> |
|
| 82 | + | Delete feed |
|
| 83 | + | </DropdownMenuItem> |
|
| 84 | + | )} |
|
| 72 | 85 | </DropdownMenuContent> |
|
| 73 | 86 | </DropdownMenu> |
|
| 74 | 87 | </div> |
|
| 33 | 33 | }); |
|
| 34 | 34 | ||
| 35 | 35 | export const allFeedsQuery = evolu.createQuery((db) => |
|
| 36 | - | db.selectFrom("rssFeed").selectAll(), |
|
| 36 | + | db |
|
| 37 | + | .selectFrom("rssFeed") |
|
| 38 | + | .selectAll() |
|
| 39 | + | .where("isDeleted", "is not", Evolu.sqliteTrue), |
|
| 37 | 40 | ); |
|
| 38 | 41 | ||
| 39 | 42 | export const postsByFeedQuery = (feedId: string) => |
|
| 42 | 45 | .selectFrom("rssPost") |
|
| 43 | 46 | .selectAll() |
|
| 44 | 47 | .where("feedId", "=", feedId as RSSFeedId) |
|
| 48 | + | .where("isDeleted", "is not", Evolu.sqliteTrue) |
|
| 45 | 49 | .orderBy("id", "desc"), |
|
| 46 | 50 | ); |
|
| 47 | 51 | ||
| 48 | 52 | export const allPostsQuery = evolu.createQuery((db) => |
|
| 49 | - | db.selectFrom("rssPost").selectAll().orderBy("id", "desc"), |
|
| 53 | + | db |
|
| 54 | + | .selectFrom("rssPost") |
|
| 55 | + | .selectAll() |
|
| 56 | + | .where("isDeleted", "is not", Evolu.sqliteTrue) |
|
| 57 | + | .orderBy("id", "desc"), |
|
| 50 | 58 | ); |
|
| 51 | 59 | ||
| 52 | 60 | export const feedsByCategoryQuery = evolu.createQuery((db) => |
|
| 53 | - | db.selectFrom("rssFeed").selectAll().orderBy("category", "asc"), |
|
| 61 | + | db |
|
| 62 | + | .selectFrom("rssFeed") |
|
| 63 | + | .selectAll() |
|
| 64 | + | .where("isDeleted", "is not", Evolu.sqliteTrue) |
|
| 65 | + | .orderBy("category", "asc"), |
|
| 54 | 66 | ); |
|
| 55 | 67 | ||
| 56 | 68 | export const allReadStatusesQuery = evolu.createQuery((db) => |
|