Skip to content

Commit bf71cba

Browse files
xmokraycastbot
andauthored
Update RSS Reader extension - remember story last read + filter by read status (close issue) (re-open PR) (#16655)
* [RSS Reader] remember story last read * [RSS Reader] ye olde lint * [RSS Reader] fix: item was always marked as read * [RSS Reader] ye olde lint * Update CHANGELOG.md and optimise images --------- Co-authored-by: raycastbot <[email protected]>
1 parent 4ad1c85 commit bf71cba

File tree

3 files changed

+66
-16
lines changed

3 files changed

+66
-16
lines changed

extensions/rss-reader/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# RSS Reader Changelog
22

3+
## [Stories Remember 'Last Read'] - 2025-02-10
4+
5+
- Filter Stories by their read status (read or unread) (ref: [Issue #16546](https://github.com/raycast/extensions/issues/16546))
6+
- Stories show an `Icon` to represent their read status
7+
- Add README.md
8+
39
## [Rename Subscriptions] - 2025-01-12
410

511
- Rename Feeds (you can restore the Original Title using the same `Form`) [ref: [#16290](https://github.com/raycast/extensions/issues/16290)]

extensions/rss-reader/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<p align="center">
2+
<img src="./assets/command-icon.png" width="150" height="150" />
3+
</p>
4+
5+
# RSS Reader
6+
7+
Conveniently read articles without leaving Raycast.
8+
9+
## ✨ Features
10+
11+
- Add Feeds
12+
- Rename Feeds
13+
- Read Stories
14+
- Open Stories

extensions/rss-reader/src/stories.tsx

+46-16
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ interface Story {
2121
isNew: boolean;
2222
date: number;
2323
fromFeed: string;
24+
lastRead?: number;
2425
}
2526

27+
type StoryLastRead = {
28+
[key: string]: number;
29+
};
2630
type FeedLastViewed = {
2731
[key: string]: number;
2832
};
@@ -49,6 +53,9 @@ function StoryListItem(props: { item: Story; refresh: () => void }) {
4953
</ActionPanel>
5054
}
5155
accessories={[
56+
props.item.lastRead
57+
? { icon: Icon.Eye, tooltip: `Last Read: ${new Date(props.item.lastRead).toDateString()}` }
58+
: { icon: Icon.EyeDisabled, tooltip: "Last Read: never" },
5259
{
5360
text: timeAgo.format(props.item.date) as string,
5461
icon: props.item.isNew ? { source: Icon.Dot, tintColor: Color.Green } : undefined,
@@ -58,14 +65,27 @@ function StoryListItem(props: { item: Story; refresh: () => void }) {
5865
);
5966
}
6067

68+
async function updateLastRead(props: { item: Story }) {
69+
const lastRead = new Date().valueOf();
70+
const storyLastViewedString = await LocalStorage.getItem<string>("storyLastRead");
71+
const storyLastRead: StoryLastRead = await JSON.parse(storyLastViewedString ?? "{}");
72+
storyLastRead[props.item.guid] = lastRead;
73+
await LocalStorage.setItem("storyLastRead", JSON.stringify(storyLastRead));
74+
}
75+
6176
function ReadStory(props: { item: Story }) {
6277
return props.item.content ? (
63-
<Action.Push icon={Icon.Book} title="Read Story" target={<StoryDetail item={props.item} />} />
78+
<Action.Push
79+
icon={Icon.Book}
80+
title="Read Story"
81+
target={<StoryDetail item={props.item} />}
82+
onPush={() => updateLastRead(props)}
83+
/>
6484
) : null;
6585
}
6686

6787
function OpenStory(props: { item: Story }) {
68-
return props.item.link ? <Action.OpenInBrowser url={props.item.link} /> : null;
88+
return props.item.link ? <Action.OpenInBrowser url={props.item.link} onOpen={() => updateLastRead(props)} /> : null;
6989
}
7090

7191
function CopyStory(props: { item: Story }) {
@@ -90,20 +110,22 @@ function ItemToStory(item: Parser.Item, feed: Feed, lastViewed: number) {
90110
}
91111

92112
async function getStories(feeds: Feed[]) {
93-
const feedLastViewedString = (await LocalStorage.getItem("feedLastViewed")) as string;
94-
const feedLastViewed = feedLastViewedString
95-
? (JSON.parse(feedLastViewedString) as FeedLastViewed)
96-
: ({} as FeedLastViewed);
113+
const feedLastViewedString = await LocalStorage.getItem<string>("feedLastViewed");
114+
const feedLastViewed: FeedLastViewed = JSON.parse(feedLastViewedString ?? "{}");
97115

98116
const storyItems: Story[] = [];
117+
const storyLastViewedString = await LocalStorage.getItem<string>("storyLastRead");
118+
const storyLastRead: StoryLastRead = JSON.parse(storyLastViewedString ?? "{}");
99119

100120
for (const feedItem of feeds) {
101121
const lastViewed = feedLastViewed[feedItem.url] || 0;
102122
try {
103123
const feed = await parser.parseURL(feedItem.url);
104124
const stories: Story[] = [];
105125
feed.items.forEach((item) => {
106-
stories.push(ItemToStory(item, feedItem, lastViewed));
126+
const story = ItemToStory(item, feedItem, lastViewed);
127+
const lastRead = storyLastRead[story.guid] || 0;
128+
stories.push({ ...story, lastRead });
107129
});
108130
feedLastViewed[feedItem.url] = stories.at(0)?.date || lastViewed;
109131
storyItems.push(...stories);
@@ -135,18 +157,22 @@ export function StoriesList(props: { feeds?: Feed[] }) {
135157
<List
136158
isLoading={isLoading}
137159
searchBarAccessory={
138-
data?.feeds.length && data.feeds.length > 1 ? (
139-
<List.Dropdown onChange={setFilter} tooltip="Subscription">
140-
<List.Dropdown.Section>
141-
<List.Dropdown.Item icon={Icon.Globe} title="All Subscriptions" value="all" />
142-
</List.Dropdown.Section>
160+
<List.Dropdown onChange={setFilter} tooltip="Subscription">
161+
<List.Dropdown.Section>
162+
<List.Dropdown.Item icon={Icon.Globe} title="All Subscriptions" value="all" />
163+
</List.Dropdown.Section>
164+
{data?.feeds && data.feeds.length > 1 && (
143165
<List.Dropdown.Section>
144-
{data?.feeds.map((feed) => (
166+
{data.feeds.map((feed) => (
145167
<List.Dropdown.Item key={feed.url} icon={feed.icon} title={feed.title} value={feed.url} />
146168
))}
147169
</List.Dropdown.Section>
148-
</List.Dropdown>
149-
) : null
170+
)}
171+
<List.Dropdown.Section>
172+
<List.Dropdown.Item icon={Icon.Eye} title="Read" value="read" />
173+
<List.Dropdown.Item icon={Icon.EyeDisabled} title="Unread" value="unread" />
174+
</List.Dropdown.Section>
175+
</List.Dropdown>
150176
}
151177
actions={
152178
!props?.feeds && (
@@ -162,7 +188,11 @@ export function StoriesList(props: { feeds?: Feed[] }) {
162188
}
163189
>
164190
{data?.stories
165-
.filter((story) => filter === "all" || story.fromFeed === filter)
191+
.filter((story) => {
192+
if (filter === "read") return story.lastRead;
193+
if (filter === "unread") return !story.lastRead;
194+
return filter === "all" || story.fromFeed === filter;
195+
})
166196
.map((story) => <StoryListItem key={story.guid} item={story} refresh={revalidate} />)}
167197
</List>
168198
);

0 commit comments

Comments
 (0)