fix: show only selected post from favorites
This commit is contained in:
@@ -89,6 +89,7 @@ export function PopularRankRow({
|
|||||||
categories,
|
categories,
|
||||||
browseSort = "popular",
|
browseSort = "popular",
|
||||||
showRank = true,
|
showRank = true,
|
||||||
|
singlePostLink = false,
|
||||||
onFavoriteChange,
|
onFavoriteChange,
|
||||||
}: {
|
}: {
|
||||||
post: Post;
|
post: Post;
|
||||||
@@ -96,6 +97,7 @@ export function PopularRankRow({
|
|||||||
categories: Category[];
|
categories: Category[];
|
||||||
browseSort?: string;
|
browseSort?: string;
|
||||||
showRank?: boolean;
|
showRank?: boolean;
|
||||||
|
singlePostLink?: boolean;
|
||||||
onFavoriteChange?: (postId: string, favorited: boolean) => void;
|
onFavoriteChange?: (postId: string, favorited: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t, lang } = useI18n();
|
const { t, lang } = useI18n();
|
||||||
@@ -142,6 +144,7 @@ export function PopularRankRow({
|
|||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (browseSort) params.set("sort", browseSort);
|
if (browseSort) params.set("sort", browseSort);
|
||||||
params.set("post", post.id);
|
params.set("post", post.id);
|
||||||
|
if (singlePostLink) params.set("single", "1");
|
||||||
navigate(lp(`/browse?${params.toString()}`));
|
navigate(lp(`/browse?${params.toString()}`));
|
||||||
}}
|
}}
|
||||||
aria-label={r.title}
|
aria-label={r.title}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
const type = sp.get("type") || "all";
|
const type = sp.get("type") || "all";
|
||||||
const q = (sp.get("q") || "").trim();
|
const q = (sp.get("q") || "").trim();
|
||||||
const sort = sp.get("sort") || "";
|
const sort = sp.get("sort") || "";
|
||||||
|
const singlePostMode = sp.get("single") === "1" && !!sp.get("post");
|
||||||
|
|
||||||
const params = useMemo(
|
const params = useMemo(
|
||||||
() => ({ scope, type, q, sort, lang }),
|
() => ({ scope, type, q, sort, lang }),
|
||||||
@@ -55,6 +56,7 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
}, [q]);
|
}, [q]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (singlePostMode) return;
|
||||||
const el = sentinelRef.current;
|
const el = sentinelRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
const io = new IntersectionObserver(
|
const io = new IntersectionObserver(
|
||||||
@@ -75,7 +77,7 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
);
|
);
|
||||||
io.observe(el);
|
io.observe(el);
|
||||||
return () => io.disconnect();
|
return () => io.disconnect();
|
||||||
}, [loadMore]);
|
}, [loadMore, singlePostMode]);
|
||||||
|
|
||||||
// When arriving with a `?post=<id>` query (or legacy `#post-<id>` hash),
|
// When arriving with a `?post=<id>` query (or legacy `#post-<id>` hash),
|
||||||
// scroll to that bubble — loading more pages until it shows up — then give
|
// 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 [isFetchingTargetPost, setIsFetchingTargetPost] = useState(false);
|
||||||
const [targetPostFetchFailed, setTargetPostFetchFailed] = useState(false);
|
const [targetPostFetchFailed, setTargetPostFetchFailed] = useState(false);
|
||||||
const targetAlreadyInBaseItems = useMemo(
|
const baseTargetPost = useMemo(
|
||||||
() =>
|
() =>
|
||||||
!!queryTargetPostId &&
|
queryTargetPostId
|
||||||
items.some((post) => post.id === queryTargetPostId),
|
? (items.find((post) => post.id === queryTargetPostId) ?? null)
|
||||||
|
: null,
|
||||||
[items, queryTargetPostId],
|
[items, queryTargetPostId],
|
||||||
);
|
);
|
||||||
|
const targetAlreadyInBaseItems = !!baseTargetPost;
|
||||||
const streamItems = useMemo(() => {
|
const streamItems = useMemo(() => {
|
||||||
|
if (singlePostMode) {
|
||||||
|
if (baseTargetPost) return [baseTargetPost];
|
||||||
|
return resolvedTargetPost ? [resolvedTargetPost] : [];
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
resolvedTargetPost &&
|
resolvedTargetPost &&
|
||||||
!items.some((post) => post.id === resolvedTargetPost.id)
|
!items.some((post) => post.id === resolvedTargetPost.id)
|
||||||
@@ -104,7 +112,7 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
return [resolvedTargetPost, ...items];
|
return [resolvedTargetPost, ...items];
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}, [items, resolvedTargetPost]);
|
}, [baseTargetPost, items, resolvedTargetPost, singlePostMode]);
|
||||||
const groups = useGroupedByDay(streamItems, lang);
|
const groups = useGroupedByDay(streamItems, lang);
|
||||||
// Lock only engages while we are actively running the smooth-scroll animation
|
// Lock only engages while we are actively running the smooth-scroll animation
|
||||||
// — not during the wait/pagination phase — so the page never feels frozen
|
// — not during the wait/pagination phase — so the page never feels frozen
|
||||||
@@ -355,13 +363,18 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
// their specific post, not just lazily loading the feed.
|
// their specific post, not just lazily loading the feed.
|
||||||
const targetInLoadedItems =
|
const targetInLoadedItems =
|
||||||
!!queryTargetPostId && streamItems.some((p) => p.id === queryTargetPostId);
|
!!queryTargetPostId && streamItems.some((p) => p.id === queryTargetPostId);
|
||||||
const isSearchingDeepTarget =
|
const isSearchingDeepTarget = singlePostMode
|
||||||
!!queryTargetPostId &&
|
? !!queryTargetPostId && !targetInLoadedItems && isFetchingTargetPost
|
||||||
|
: !!queryTargetPostId &&
|
||||||
!targetInLoadedItems &&
|
!targetInLoadedItems &&
|
||||||
!error &&
|
!error &&
|
||||||
(isFetchingTargetPost || hasMore || isLoading);
|
(isFetchingTargetPost || hasMore || isLoading);
|
||||||
const targetNotFoundInStream =
|
const targetNotFoundInStream = singlePostMode
|
||||||
!!queryTargetPostId &&
|
? !!queryTargetPostId &&
|
||||||
|
!targetInLoadedItems &&
|
||||||
|
targetPostFetchFailed &&
|
||||||
|
!isFetchingTargetPost
|
||||||
|
: !!queryTargetPostId &&
|
||||||
!targetInLoadedItems &&
|
!targetInLoadedItems &&
|
||||||
!error &&
|
!error &&
|
||||||
targetPostFetchFailed &&
|
targetPostFetchFailed &&
|
||||||
@@ -373,12 +386,17 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
<div className="mx-auto max-w-full md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
|
<div className="mx-auto max-w-full md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
|
||||||
{/* Filters stay pinned below the global header (which shows the page
|
{/* Filters stay pinned below the global header (which shows the page
|
||||||
name) so users can switch filters while scrolling. */}
|
name) so users can switch filters while scrolling. */}
|
||||||
|
{!singlePostMode ? (
|
||||||
<div
|
<div
|
||||||
ref={filterBarRef}
|
ref={filterBarRef}
|
||||||
className="sticky top-[64px] z-30 bg-ark-bg md:top-[70px]"
|
className="sticky top-[64px] z-30 bg-ark-bg md:top-[70px]"
|
||||||
>
|
>
|
||||||
<FilterChips type={type} onTypeChange={(v) => updateParam("type", v)} />
|
<FilterChips
|
||||||
|
type={type}
|
||||||
|
onTypeChange={(v) => updateParam("type", v)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 px-4 pt-4 md:px-0 md:pt-2">
|
<div className="flex flex-col gap-3 px-4 pt-4 md:px-0 md:pt-2">
|
||||||
{isSearchingDeepTarget ? (
|
{isSearchingDeepTarget ? (
|
||||||
@@ -432,7 +450,7 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{error ? (
|
{!singlePostMode && error ? (
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
className="my-4 flex flex-col gap-3 rounded-xl border border-red-900 bg-red-950/40 px-4 py-3 text-sm text-red-200 sm:flex-row sm:items-center sm:justify-between"
|
className="my-4 flex flex-col gap-3 rounded-xl border border-red-900 bg-red-950/40 px-4 py-3 text-sm text-red-200 sm:flex-row sm:items-center sm:justify-between"
|
||||||
@@ -450,7 +468,7 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{isLoading && !error ? (
|
{!singlePostMode && isLoading && !error ? (
|
||||||
<div
|
<div
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
aria-label={t("loading")}
|
aria-label={t("loading")}
|
||||||
@@ -462,7 +480,9 @@ export function MessageStream({ scope }: MessageStreamProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!singlePostMode ? (
|
||||||
<div ref={sentinelRef} aria-hidden className="h-1" />
|
<div ref={sentinelRef} aria-hidden className="h-1" />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export default function Favorites() {
|
|||||||
categories={categories}
|
categories={categories}
|
||||||
browseSort=""
|
browseSort=""
|
||||||
showRank={false}
|
showRank={false}
|
||||||
|
singlePostLink
|
||||||
onFavoriteChange={(_, favorited) => {
|
onFavoriteChange={(_, favorited) => {
|
||||||
if (!favorited) setReloadKey((value) => value + 1);
|
if (!favorited) setReloadKey((value) => value + 1);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user