| 37 |
37 |
|
allFeedsQuery, |
| 38 |
38 |
|
allPostsQuery, |
| 39 |
39 |
|
postsByFeedQuery, |
|
40 |
+ |
allReadStatusesQuery, |
| 40 |
41 |
|
useEvolu, |
| 41 |
42 |
|
reset, |
| 42 |
43 |
|
} from "@/lib/evolu"; |
|
| 79 |
80 |
|
|
| 80 |
81 |
|
const { insert, update } = useEvolu(); |
| 81 |
82 |
|
const allFeeds = useQuery(allFeedsQuery); |
|
83 |
+ |
const allReadStatuses = useQuery(allReadStatusesQuery); |
| 82 |
84 |
|
|
| 83 |
85 |
|
// Get posts based on selected feed |
| 84 |
86 |
|
const allPosts = useQuery(allPostsQuery); |
|
| 102 |
104 |
|
return b.publishedDate.localeCompare(a.publishedDate); |
| 103 |
105 |
|
}); |
| 104 |
106 |
|
}, [feedPosts, searchQuery]); |
|
107 |
+ |
|
|
108 |
+ |
// Check if a post is read |
|
109 |
+ |
const isPostRead = React.useCallback( |
|
110 |
+ |
(postId: string) => { |
|
111 |
+ |
return allReadStatuses.some((status) => status.postId === postId); |
|
112 |
+ |
}, |
|
113 |
+ |
[allReadStatuses], |
|
114 |
+ |
); |
|
115 |
+ |
|
|
116 |
+ |
// Handle post selection and mark as read |
|
117 |
+ |
const handlePostSelect = React.useCallback( |
|
118 |
+ |
(postId: string) => { |
|
119 |
+ |
// Mark as read if not already read |
|
120 |
+ |
if (!isPostRead(postId)) { |
|
121 |
+ |
const post = feedPosts.find((p) => p.id === postId); |
|
122 |
+ |
if (post) { |
|
123 |
+ |
insert("readStatus", { |
|
124 |
+ |
postId: postId as any, |
|
125 |
+ |
feedId: post.feedId, |
|
126 |
+ |
}); |
|
127 |
+ |
} |
|
128 |
+ |
} |
|
129 |
+ |
// Call the original onPostSelect |
|
130 |
+ |
onPostSelect(postId); |
|
131 |
+ |
}, |
|
132 |
+ |
[isPostRead, feedPosts, insert, onPostSelect], |
|
133 |
+ |
); |
| 105 |
134 |
|
|
| 106 |
135 |
|
async function addFeed() { |
| 107 |
136 |
|
if (!urlInput.trim()) { |
|
| 377 |
406 |
|
No posts found |
| 378 |
407 |
|
</div> |
| 379 |
408 |
|
) : ( |
| 380 |
|
- |
filteredPosts.map((post) => ( |
| 381 |
|
- |
<button |
| 382 |
|
- |
key={post.id} |
| 383 |
|
- |
onClick={() => onPostSelect(post.id)} |
| 384 |
|
- |
className={`hover:bg-sidebar-accent flex flex-col items-start gap-1.5 border-b px-3 py-3 text-sm text-left w-full last:border-b-0 transition-colors ${ |
| 385 |
|
- |
selectedPostId === post.id ? "bg-sidebar-accent" : "" |
| 386 |
|
- |
}`} |
| 387 |
|
- |
> |
| 388 |
|
- |
<span className="font-medium line-clamp-2 leading-snug"> |
| 389 |
|
- |
{post.title} |
| 390 |
|
- |
</span> |
| 391 |
|
- |
{post.author && ( |
| 392 |
|
- |
<span className="text-muted-foreground text-xs"> |
| 393 |
|
- |
{post.author} |
| 394 |
|
- |
</span> |
| 395 |
|
- |
)} |
| 396 |
|
- |
</button> |
| 397 |
|
- |
)) |
|
409 |
+ |
filteredPosts.map((post) => { |
|
410 |
+ |
const isRead = isPostRead(post.id); |
|
411 |
+ |
return ( |
|
412 |
+ |
<button |
|
413 |
+ |
key={post.id} |
|
414 |
+ |
onClick={() => handlePostSelect(post.id)} |
|
415 |
+ |
className={`hover:bg-sidebar-accent flex items-start gap-2 border-b px-3 py-3 text-sm text-left w-full last:border-b-0 transition-colors ${ |
|
416 |
+ |
selectedPostId === post.id ? "bg-sidebar-accent" : "" |
|
417 |
+ |
}`} |
|
418 |
+ |
> |
|
419 |
+ |
{/* Unread indicator */} |
|
420 |
+ |
<div className="flex-shrink-0 pt-1"> |
|
421 |
+ |
{!isRead && ( |
|
422 |
+ |
<div className="size-2 rounded-full bg-primary" /> |
|
423 |
+ |
)} |
|
424 |
+ |
</div> |
|
425 |
+ |
{/* Post content */} |
|
426 |
+ |
<div className="flex flex-col gap-1.5 flex-1 min-w-0"> |
|
427 |
+ |
<span className="font-medium line-clamp-2 leading-snug"> |
|
428 |
+ |
{post.title} |
|
429 |
+ |
</span> |
|
430 |
+ |
{post.author && ( |
|
431 |
+ |
<span className="text-muted-foreground text-xs"> |
|
432 |
+ |
{post.author} |
|
433 |
+ |
</span> |
|
434 |
+ |
)} |
|
435 |
+ |
</div> |
|
436 |
+ |
</button> |
|
437 |
+ |
); |
|
438 |
+ |
}) |
| 398 |
439 |
|
)} |
| 399 |
440 |
|
</div> |
| 400 |
441 |
|
</div> |