From 9f5367ae12997a331dbbbf4220c485a33b148f50 Mon Sep 17 00:00:00 2001 From: TerryM Date: Fri, 5 Jun 2026 18:52:25 +0800 Subject: [PATCH] fix: show only selected post from favorites --- src/components/PopularRankList.tsx | 3 + .../messageStream/MessageStream.tsx | 74 ++++++++++++------- src/pages/Favorites/index.tsx | 1 + 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/components/PopularRankList.tsx b/src/components/PopularRankList.tsx index f73b295..a68f119 100644 --- a/src/components/PopularRankList.tsx +++ b/src/components/PopularRankList.tsx @@ -89,6 +89,7 @@ export function PopularRankRow({ categories, browseSort = "popular", showRank = true, + singlePostLink = false, onFavoriteChange, }: { post: Post; @@ -96,6 +97,7 @@ export function PopularRankRow({ categories: Category[]; browseSort?: string; showRank?: boolean; + singlePostLink?: boolean; onFavoriteChange?: (postId: string, favorited: boolean) => void; }) { const { t, lang } = useI18n(); @@ -142,6 +144,7 @@ export function PopularRankRow({ const params = new URLSearchParams(); if (browseSort) params.set("sort", browseSort); params.set("post", post.id); + if (singlePostLink) params.set("single", "1"); navigate(lp(`/browse?${params.toString()}`)); }} aria-label={r.title} diff --git a/src/components/messageStream/MessageStream.tsx b/src/components/messageStream/MessageStream.tsx index dac45ae..f37a2d5 100644 --- a/src/components/messageStream/MessageStream.tsx +++ b/src/components/messageStream/MessageStream.tsx @@ -24,6 +24,7 @@ export function MessageStream({ scope }: MessageStreamProps) { const type = sp.get("type") || "all"; const q = (sp.get("q") || "").trim(); const sort = sp.get("sort") || ""; + const singlePostMode = sp.get("single") === "1" && !!sp.get("post"); const params = useMemo( () => ({ scope, type, q, sort, lang }), @@ -55,6 +56,7 @@ export function MessageStream({ scope }: MessageStreamProps) { }, [q]); useEffect(() => { + if (singlePostMode) return; const el = sentinelRef.current; if (!el) return; const io = new IntersectionObserver( @@ -75,7 +77,7 @@ export function MessageStream({ scope }: MessageStreamProps) { ); io.observe(el); return () => io.disconnect(); - }, [loadMore]); + }, [loadMore, singlePostMode]); // When arriving with a `?post=` query (or legacy `#post-` hash), // scroll to that bubble — loading more pages until it shows up — then give @@ -90,13 +92,19 @@ export function MessageStream({ scope }: MessageStreamProps) { ); const [isFetchingTargetPost, setIsFetchingTargetPost] = useState(false); const [targetPostFetchFailed, setTargetPostFetchFailed] = useState(false); - const targetAlreadyInBaseItems = useMemo( + const baseTargetPost = useMemo( () => - !!queryTargetPostId && - items.some((post) => post.id === queryTargetPostId), + queryTargetPostId + ? (items.find((post) => post.id === queryTargetPostId) ?? null) + : null, [items, queryTargetPostId], ); + const targetAlreadyInBaseItems = !!baseTargetPost; const streamItems = useMemo(() => { + if (singlePostMode) { + if (baseTargetPost) return [baseTargetPost]; + return resolvedTargetPost ? [resolvedTargetPost] : []; + } if ( resolvedTargetPost && !items.some((post) => post.id === resolvedTargetPost.id) @@ -104,7 +112,7 @@ export function MessageStream({ scope }: MessageStreamProps) { return [resolvedTargetPost, ...items]; } return items; - }, [items, resolvedTargetPost]); + }, [baseTargetPost, items, resolvedTargetPost, singlePostMode]); const groups = useGroupedByDay(streamItems, lang); // Lock only engages while we are actively running the smooth-scroll animation // — not during the wait/pagination phase — so the page never feels frozen @@ -355,30 +363,40 @@ export function MessageStream({ scope }: MessageStreamProps) { // their specific post, not just lazily loading the feed. const targetInLoadedItems = !!queryTargetPostId && streamItems.some((p) => p.id === queryTargetPostId); - const isSearchingDeepTarget = - !!queryTargetPostId && - !targetInLoadedItems && - !error && - (isFetchingTargetPost || hasMore || isLoading); - const targetNotFoundInStream = - !!queryTargetPostId && - !targetInLoadedItems && - !error && - targetPostFetchFailed && - !hasMore && - !isLoading && - streamItems.length > 0; + const isSearchingDeepTarget = singlePostMode + ? !!queryTargetPostId && !targetInLoadedItems && isFetchingTargetPost + : !!queryTargetPostId && + !targetInLoadedItems && + !error && + (isFetchingTargetPost || hasMore || isLoading); + const targetNotFoundInStream = singlePostMode + ? !!queryTargetPostId && + !targetInLoadedItems && + targetPostFetchFailed && + !isFetchingTargetPost + : !!queryTargetPostId && + !targetInLoadedItems && + !error && + targetPostFetchFailed && + !hasMore && + !isLoading && + streamItems.length > 0; return (
{/* Filters stay pinned below the global header (which shows the page name) so users can switch filters while scrolling. */} -
- updateParam("type", v)} /> -
+ {!singlePostMode ? ( +
+ updateParam("type", v)} + /> +
+ ) : null}
{isSearchingDeepTarget ? ( @@ -432,7 +450,7 @@ export function MessageStream({ scope }: MessageStreamProps) {

) : null} - {error ? ( + {!singlePostMode && error ? (
) : null} - {isLoading && !error ? ( + {!singlePostMode && isLoading && !error ? (
)} -
+ {!singlePostMode ? ( +
+ ) : null}
); diff --git a/src/pages/Favorites/index.tsx b/src/pages/Favorites/index.tsx index 8245ecd..1514f7c 100644 --- a/src/pages/Favorites/index.tsx +++ b/src/pages/Favorites/index.tsx @@ -169,6 +169,7 @@ export default function Favorites() { categories={categories} browseSort="" showRank={false} + singlePostLink onFavoriteChange={(_, favorited) => { if (!favorited) setReloadKey((value) => value + 1); }}