From 7c595cde830ba64046cf12860d49d3dc064e7d4f Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Wed, 8 Apr 2026 02:00:16 -0500 Subject: [PATCH] feat: dedicated post navigation and routing --- src/App.tsx | 7 +- src/components/feeds/FeedComposer.tsx | 1 + src/components/feeds/PostCard.tsx | 145 ++++++-- src/components/feeds/embeds/ContentEmbed.tsx | 31 +- src/components/feeds/tests/PostCard.test.tsx | 81 +++- .../notifications/NotificationsPanel.test.tsx | 74 +++- .../notifications/NotificationsPanel.tsx | 52 +-- src/components/posts/PostPanel.test.tsx | 140 +++++++ src/components/posts/PostPanel.tsx | 352 ++++++++++++++++++ src/components/posts/ThreadDrawer.test.tsx | 170 +++++++++ .../{ThreadModal.tsx => ThreadDrawer.tsx} | 147 ++++++-- src/components/posts/ThreadModal.test.tsx | 71 ---- src/components/posts/usePostNavigation.ts | 21 ++ .../posts/useThreadOverlayNavigation.test.ts | 52 +++ .../posts/useThreadOverlayNavigation.ts | 52 ++- src/components/profile/ProfilePanel.test.tsx | 21 +- src/components/profile/ProfilePanel.tsx | 8 +- src/components/saved/SavedPostsPanel.test.tsx | 7 +- src/components/saved/SavedPostsPanel.tsx | 8 +- src/components/search/HashtagPanel.test.tsx | 9 +- src/components/search/HashtagPanel.tsx | 6 +- src/components/search/SearchPanel.test.tsx | 9 +- src/components/search/SearchPanel.tsx | 56 ++- src/components/shared/QuotedPostPreview.tsx | 72 +++- src/lib/feeds.ts | 17 + src/lib/post-routes.test.ts | 30 ++ src/lib/post-routes.ts | 23 ++ src/router.test.tsx | 14 +- src/router.tsx | 14 + 29 files changed, 1423 insertions(+), 267 deletions(-) create mode 100644 src/components/posts/PostPanel.test.tsx create mode 100644 src/components/posts/PostPanel.tsx create mode 100644 src/components/posts/ThreadDrawer.test.tsx rename src/components/posts/{ThreadModal.tsx => ThreadDrawer.tsx} (71%) delete mode 100644 src/components/posts/ThreadModal.test.tsx create mode 100644 src/components/posts/usePostNavigation.ts create mode 100644 src/components/posts/useThreadOverlayNavigation.test.ts create mode 100644 src/lib/post-routes.test.ts create mode 100644 src/lib/post-routes.ts diff --git a/src/App.tsx b/src/App.tsx index 606d422..0bc941a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,8 @@ import { LoginPanel } from "./components/LoginPanel"; import { MessagesPanel } from "./components/messages/MessagesPanel"; import { NotificationsPanel } from "./components/notifications/NotificationsPanel"; import { HeaderPanel } from "./components/panels/Header"; -import { ThreadModal } from "./components/posts/ThreadModal"; +import { PostPanel } from "./components/posts/PostPanel"; +import { ThreadDrawer } from "./components/posts/ThreadDrawer"; import { ProfilePanel } from "./components/profile/ProfilePanel"; import { AppRail } from "./components/rail/AppRail"; import { SessionSpotlight } from "./components/Session"; @@ -66,8 +67,7 @@ function AppShell(props: AppShellProps) { {props.children} - - + ); @@ -86,6 +86,7 @@ function AppContent() { renderComposer={() => } renderMessages={(props) => } renderNotifications={() => } + renderPost={(props) => } renderProfile={(props) => } renderShell={AppShell} renderTimeline={() => } /> diff --git a/src/components/feeds/FeedComposer.tsx b/src/components/feeds/FeedComposer.tsx index c87c447..5e8bf3c 100644 --- a/src/components/feeds/FeedComposer.tsx +++ b/src/components/feeds/FeedComposer.tsx @@ -361,6 +361,7 @@ function QuotePreview(props: { post: PostView | null }) { class="mt-4 rounded-2xl bg-black/30 p-4 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]" href={buildPublicPostUrl(post())} text={getPostText(post()) || "Quoted post"} + truncate title="Quote preview" /> )} diff --git a/src/components/feeds/PostCard.tsx b/src/components/feeds/PostCard.tsx index 871428f..1459e3d 100644 --- a/src/components/feeds/PostCard.tsx +++ b/src/components/feeds/PostCard.tsx @@ -15,11 +15,13 @@ import { getPostCreatedAt, getPostFacets, getPostText, + hasKnownThreadContext, isReplyItem, } from "$/lib/feeds"; import { collectModerationLabels } from "$/lib/moderation"; import { buildProfileRoute, getProfileRouteActor } from "$/lib/profile"; import type { + EmbedView, FeedViewPost, ModerationLabel, ModerationReasonType, @@ -38,21 +40,28 @@ function isInteractiveTarget(target: EventTarget | null) { return target instanceof Element && !!target.closest("a, button, input, textarea, select, [role='menuitem']"); } -function PostHeader(props: { authorHandle: string; authorName: string; createdAt: string; profileHref: string }) { +function isDecisionHidden(decision: ModerationUiDecision) { + return decision.filter || decision.blur !== "none"; +} + +function mergeModerationDecisions( + contentDecision: ModerationUiDecision, + mediaDecision: ModerationUiDecision, +): ModerationUiDecision { + return { + alert: contentDecision.alert || mediaDecision.alert, + blur: contentDecision.blur !== "none" ? contentDecision.blur : mediaDecision.blur, + filter: contentDecision.filter || mediaDecision.filter, + inform: contentDecision.inform || mediaDecision.inform, + noOverride: contentDecision.noOverride || mediaDecision.noOverride, + }; +} + +function PostHeader(props: { authorHandle: string; authorName: string; createdAt: string }) { return (
- event.stopPropagation()}> - {props.authorName} - - event.stopPropagation()}> - {props.authorHandle} - + {props.authorName} + {props.authorHandle} {props.createdAt}
); @@ -147,11 +156,14 @@ type PostActionsProps = { onOpen: (element: HTMLButtonElement) => void; triggerRef: (element: HTMLButtonElement) => void; }; + showThreadAction: boolean; state: PostActionStatus; }; function PostActions(props: PostActionsProps) { - const [status, menu, actions] = splitProps(props, ["state"], ["menu"], ["handlers"]); + const [status, menu, actions, visibility] = splitProps(props, ["state"], ["menu"], ["handlers"], [ + "showThreadAction", + ]); return (