Skip to content

Commit 1739208

Browse files
committed
feat: fetch stream entry content
Signed-off-by: Innei <tukon479@gmail.com>
1 parent c8d1230 commit 1739208

File tree

5 files changed

+134
-15
lines changed

5 files changed

+134
-15
lines changed

apps/renderer/src/modules/entry-column/hooks.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { views } from "@follow/constants"
2+
import { useMutation } from "@tanstack/react-query"
23
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
34
import type { ListRange } from "react-virtuoso"
45
import { useDebounceCallback } from "usehooks-ts"
56

67
import { useGeneralSettingKey } from "~/atoms/settings/general"
78
import { useRouteParams, useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
89
import { useAuthQuery } from "~/hooks/common"
10+
import { apiClient, apiFetch } from "~/lib/api-fetch"
911
import { entries, useEntries } from "~/queries/entries"
10-
import { entryActions, useEntryIdsByFeedIdOrView } from "~/store/entry"
12+
import { entryActions, getEntry, useEntryIdsByFeedIdOrView } from "~/store/entry"
1113
import { useFolderFeedsByFeedId } from "~/store/subscription"
1214
import { feedUnreadActions } from "~/store/unread"
1315

@@ -114,6 +116,8 @@ export const useEntriesByView = ({
114116
] as string[]
115117
}, [query.data?.pages])
116118

119+
useFetchEntryContentByStream(remoteEntryIds)
120+
117121
const currentEntries = useEntryIdsByFeedIdOrView(isAllFeeds ? view : folderIds || feedId!, {
118122
unread: unreadOnly,
119123
view,
@@ -273,3 +277,80 @@ function sortEntriesIdByEntryInsertedAt(entries: string[]) {
273277
entriesId2Map[b]?.entries.insertedAt.localeCompare(entriesId2Map[a]?.entries.insertedAt),
274278
)
275279
}
280+
281+
const useFetchEntryContentByStream = (remoteEntryIds: string[]) => {
282+
const { mutate: updateEntryContent } = useMutation({
283+
mutationKey: ["stream-entry-content", remoteEntryIds],
284+
mutationFn: async (remoteEntryIds: string[]) => {
285+
const onlyNoStored = true
286+
287+
const nextIds = [] as string[]
288+
if (onlyNoStored) {
289+
for (const id of remoteEntryIds) {
290+
const entry = getEntry(id)
291+
if (entry.entries.content) {
292+
continue
293+
}
294+
295+
nextIds.push(id)
296+
}
297+
}
298+
299+
const readStream = async () => {
300+
const response = await apiFetch(apiClient.entries.stream.$url().toString(), {
301+
method: "post",
302+
body: JSON.stringify({
303+
ids: nextIds,
304+
}),
305+
responseType: "stream",
306+
})
307+
308+
const reader = response.getReader()
309+
if (!reader) return
310+
311+
const decoder = new TextDecoder()
312+
let buffer = ""
313+
314+
try {
315+
while (true) {
316+
const { done, value } = await reader.read()
317+
if (done) break
318+
319+
buffer += decoder.decode(value, { stream: true })
320+
const lines = buffer.split("\n")
321+
322+
// Process all complete lines
323+
for (let i = 0; i < lines.length - 1; i++) {
324+
if (lines[i].trim()) {
325+
const json = JSON.parse(lines[i])
326+
// Handle each JSON line here
327+
entryActions.updateEntryContent(json.id, json.content)
328+
}
329+
}
330+
331+
// Keep the last incomplete line in the buffer
332+
buffer = lines.at(-1) || ""
333+
}
334+
335+
// Process any remaining data
336+
if (buffer.trim()) {
337+
const json = JSON.parse(buffer)
338+
339+
entryActions.updateEntryContent(json.id, json.content)
340+
}
341+
} catch (error) {
342+
console.error("Error reading stream:", error)
343+
} finally {
344+
reader.releaseLock()
345+
}
346+
}
347+
348+
readStream()
349+
},
350+
})
351+
352+
useEffect(() => {
353+
if (!remoteEntryIds) return
354+
updateEntryContent(remoteEntryIds)
355+
}, [remoteEntryIds, updateEntryContent])
356+
}

apps/renderer/src/modules/entry-column/templates/list-item-template.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js"
22
import { cn, isSafari } from "@follow/utils/utils"
3-
import { useDebounceCallback } from "usehooks-ts"
43

54
import { AudioPlayer, useAudioPlayerAtomSelector } from "~/atoms/player"
65
import { useRealInWideMode, useUISettingKey } from "~/atoms/settings/ui"
@@ -12,7 +11,6 @@ import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
1211
import { EntryTranslation } from "~/modules/entry-column/translation"
1312
import { FeedIcon } from "~/modules/feed/feed-icon"
1413
import { FeedTitle } from "~/modules/feed/feed-title"
15-
import { Queries } from "~/queries"
1614
import { useEntry } from "~/store/entry/hooks"
1715
import { getPreferredTitle, useFeedById } from "~/store/feed"
1816
import { useInboxById } from "~/store/inbox"
@@ -52,15 +50,15 @@ export function ListItem({
5250

5351
const inbox = useInboxById(entry?.inboxId)
5452

55-
const handlePrefetchEntry = useDebounceCallback(
56-
() => {
57-
inbox
58-
? Queries.entries.byInboxId(entryId).prefetch()
59-
: Queries.entries.byId(entryId).prefetch()
60-
},
61-
300,
62-
{ leading: false },
63-
)
53+
// const handlePrefetchEntry = useDebounceCallback(
54+
// () => {
55+
// inbox
56+
// ? Queries.entries.byInboxId(entryId).prefetch()
57+
// : Queries.entries.byId(entryId).prefetch()
58+
// },
59+
// 300,
60+
// { leading: false },
61+
// )
6462

6563
const settingWideMode = useRealInWideMode()
6664
const thumbnailRatio = useUISettingKey("thumbnailRatio")
@@ -76,8 +74,8 @@ export function ListItem({
7674

7775
return (
7876
<div
79-
onMouseEnter={handlePrefetchEntry}
80-
onMouseLeave={handlePrefetchEntry.cancel}
77+
// onMouseEnter={handlePrefetchEntry}
78+
// onMouseLeave={handlePrefetchEntry.cancel}
8179
className={cn(
8280
"group relative flex cursor-menu pl-3 pr-2",
8381
!asRead &&

apps/renderer/src/store/entry/store.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ class EntryActions {
5959
runTransactionInScope(() => EntryService.deleteEntries(entryIds))
6060
}
6161

62+
updateEntryContent(entryId: string, content: string) {
63+
set((state) =>
64+
produce(state, (draft) => {
65+
if (!draft.flatMapEntries[entryId]) return
66+
draft.flatMapEntries[entryId].entries.content = content
67+
}),
68+
)
69+
}
70+
6271
async fetchEntryById(entryId: string) {
6372
const { data } = await apiClient.entries.$get({
6473
query: {

locales/errors/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"1": "Previous operation is not completed",
33
"2": "CSRF Token mismatch",
4+
"3": "Unprocessable content",
45
"1000": "Unauthorized",
56
"1001": "create session failed",
67
"1002": "Invalid parameter",
@@ -50,5 +51,6 @@
5051
"10001": "Inbox already exists",
5152
"10002": "Inbox limit exceeded",
5253
"10003": "Inbox permission denied",
53-
"11000": "RSSHub route not found"
54+
"11000": "RSSHub route not found",
55+
"12000": "Action limit exceeded"
5456
}

packages/shared/src/hono.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6433,6 +6433,20 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
64336433
status: 200;
64346434
};
64356435
};
6436+
"/feeds/reset": {
6437+
$get: {
6438+
input: {
6439+
query: {
6440+
id: string | string[];
6441+
};
6442+
};
6443+
output: {
6444+
code: 0;
6445+
};
6446+
outputFormat: "json" | "text";
6447+
status: 200;
6448+
};
6449+
};
64366450
} & {
64376451
"/entries/inbox": {
64386452
$post: {
@@ -6854,6 +6868,18 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
68546868
status: 200;
68556869
};
68566870
};
6871+
"/entries/stream": {
6872+
$post: {
6873+
input: {
6874+
json: {
6875+
ids: string[];
6876+
};
6877+
};
6878+
output: {};
6879+
outputFormat: string;
6880+
status: 200;
6881+
};
6882+
};
68576883
"/entries/preview": {
68586884
$get: {
68596885
input: {
@@ -7043,7 +7069,9 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
70437069
input: {
70447070
query: {
70457071
category?: string | string[] | undefined;
7072+
categories?: string | string[] | undefined;
70467073
namespace?: string | string[] | undefined;
7074+
lang?: string | string[] | undefined;
70477075
};
70487076
};
70497077
output: {
@@ -7052,6 +7080,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
70527080
description: string;
70537081
name: string;
70547082
url: string;
7083+
lang: string;
70557084
routes: {
70567085
[x: string]: {
70577086
description: string;

0 commit comments

Comments
 (0)