diff --git a/src/components/InfiniteCustomFeed.tsx b/src/components/InfiniteCustomFeed.tsx index 1cbd95c..93a9ccc 100644 --- a/src/components/InfiniteCustomFeed.tsx +++ b/src/components/InfiniteCustomFeed.tsx @@ -14,15 +14,19 @@ interface InfiniteCustomFeedProps { feedUri: string; pdsUrl?: string; feedServiceDid?: string; + authedOverride?: boolean; + unauthedfeedurl?: string; } export function InfiniteCustomFeed({ feedUri, pdsUrl, feedServiceDid, + authedOverride, + unauthedfeedurl, }: InfiniteCustomFeedProps) { const { agent } = useAuth(); - const authed = !!agent?.did; + const authed = authedOverride || !!agent?.did; // const identityresultmaybe = useQueryIdentity(agent?.did); // const identity = identityresultmaybe?.data; @@ -45,6 +49,7 @@ export function InfiniteCustomFeed({ isAuthed: authed ?? false, pdsUrl: pdsUrl, feedServiceDid: feedServiceDid, + unauthedfeedurl: unauthedfeedurl, }); const queryClient = useQueryClient(); diff --git a/src/components/UniversalPostRenderer.tsx b/src/components/UniversalPostRenderer.tsx index 819548d..7672881 100644 --- a/src/components/UniversalPostRenderer.tsx +++ b/src/components/UniversalPostRenderer.tsx @@ -1204,7 +1204,7 @@ import ReactPlayer from "react-player"; import defaultpfp from "~/../public/favicon.png"; import { useAuth } from "~/providers/UnifiedAuthProvider"; -import { FollowButton, Mutual } from "~/routes/profile.$did"; +import { FeedItemRenderAturiLoader, FollowButton, Mutual } from "~/routes/profile.$did"; import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i"; // import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed"; // import type { @@ -2179,6 +2179,10 @@ function PostEmbeds({ } if (AppBskyEmbedRecord.isView(embed)) { + // hey im really lazy and im gonna do it the bad way + const reallybaduri = (embed?.record as any)?.uri as string | undefined; + const reallybadaturi = reallybaduri ? new AtUri(reallybaduri) : undefined; + // custom feed embed (i.e. generator view) if (AppBskyFeedDefs.isGeneratorView(embed.record)) { // stopgap sorry @@ -2188,6 +2192,8 @@ function PostEmbeds({ // // // ) + } else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.feed.generator") { + return
} // list embed @@ -2199,6 +2205,8 @@ function PostEmbeds({ // // // ) + } else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.list") { + return
} // starter pack embed @@ -2210,6 +2218,8 @@ function PostEmbeds({ // // // ) + } else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.starterpack") { + return
} // quote post @@ -2269,6 +2279,7 @@ function PostEmbeds({ ); } else { + console.log("what the hell is a ", embed); return <>sorry; } //return ; diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 6508898..7466e35 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -21,6 +21,7 @@ import { Route as ProfileDidIndexRouteImport } from './routes/profile.$did/index import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey' +import { Route as ProfileDidFeedRkeyRouteImport } from './routes/profile.$did/feed.$rkey' import { Route as ProfileDidPostRkeyRepostedByRouteImport } from './routes/profile.$did/post.$rkey.reposted-by' import { Route as ProfileDidPostRkeyQuotesRouteImport } from './routes/profile.$did/post.$rkey.quotes' import { Route as ProfileDidPostRkeyLikedByRouteImport } from './routes/profile.$did/post.$rkey.liked-by' @@ -87,6 +88,11 @@ const ProfileDidPostRkeyRoute = ProfileDidPostRkeyRouteImport.update({ path: '/profile/$did/post/$rkey', getParentRoute: () => rootRouteImport, } as any) +const ProfileDidFeedRkeyRoute = ProfileDidFeedRkeyRouteImport.update({ + id: '/profile/$did/feed/$rkey', + path: '/profile/$did/feed/$rkey', + getParentRoute: () => rootRouteImport, +} as any) const ProfileDidPostRkeyRepostedByRoute = ProfileDidPostRkeyRepostedByRouteImport.update({ id: '/reposted-by', @@ -122,6 +128,7 @@ export interface FileRoutesByFullPath { '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute '/profile/$did': typeof ProfileDidIndexRoute + '/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren '/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute '/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute @@ -138,6 +145,7 @@ export interface FileRoutesByTo { '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute '/profile/$did': typeof ProfileDidIndexRoute + '/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren '/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute '/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute @@ -157,6 +165,7 @@ export interface FileRoutesById { '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute '/profile/$did/': typeof ProfileDidIndexRoute + '/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren '/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute '/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute @@ -175,6 +184,7 @@ export interface FileRouteTypes { | '/route-a' | '/route-b' | '/profile/$did' + | '/profile/$did/feed/$rkey' | '/profile/$did/post/$rkey' | '/profile/$did/post/$rkey/liked-by' | '/profile/$did/post/$rkey/quotes' @@ -191,6 +201,7 @@ export interface FileRouteTypes { | '/route-a' | '/route-b' | '/profile/$did' + | '/profile/$did/feed/$rkey' | '/profile/$did/post/$rkey' | '/profile/$did/post/$rkey/liked-by' | '/profile/$did/post/$rkey/quotes' @@ -209,6 +220,7 @@ export interface FileRouteTypes { | '/_pathlessLayout/_nested-layout/route-a' | '/_pathlessLayout/_nested-layout/route-b' | '/profile/$did/' + | '/profile/$did/feed/$rkey' | '/profile/$did/post/$rkey' | '/profile/$did/post/$rkey/liked-by' | '/profile/$did/post/$rkey/quotes' @@ -225,6 +237,7 @@ export interface RootRouteChildren { SettingsRoute: typeof SettingsRoute CallbackIndexRoute: typeof CallbackIndexRoute ProfileDidIndexRoute: typeof ProfileDidIndexRoute + ProfileDidFeedRkeyRoute: typeof ProfileDidFeedRkeyRoute ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRouteWithChildren } @@ -314,6 +327,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProfileDidPostRkeyRouteImport parentRoute: typeof rootRouteImport } + '/profile/$did/feed/$rkey': { + id: '/profile/$did/feed/$rkey' + path: '/profile/$did/feed/$rkey' + fullPath: '/profile/$did/feed/$rkey' + preLoaderRoute: typeof ProfileDidFeedRkeyRouteImport + parentRoute: typeof rootRouteImport + } '/profile/$did/post/$rkey/reposted-by': { id: '/profile/$did/post/$rkey/reposted-by' path: '/reposted-by' @@ -401,6 +421,7 @@ const rootRouteChildren: RootRouteChildren = { SettingsRoute: SettingsRoute, CallbackIndexRoute: CallbackIndexRoute, ProfileDidIndexRoute: ProfileDidIndexRoute, + ProfileDidFeedRkeyRoute: ProfileDidFeedRkeyRoute, ProfileDidPostRkeyRoute: ProfileDidPostRkeyRouteWithChildren, } export const routeTree = rootRouteImport diff --git a/src/routes/profile.$did/feed.$rkey.tsx b/src/routes/profile.$did/feed.$rkey.tsx new file mode 100644 index 0000000..f9d2ff8 --- /dev/null +++ b/src/routes/profile.$did/feed.$rkey.tsx @@ -0,0 +1,90 @@ +import * as ATPAPI from "@atproto/api"; +import { AtUri } from "@atproto/api"; +import { createFileRoute } from "@tanstack/react-router"; +import { useAtom } from "jotai"; + +import { Header } from "~/components/Header"; +import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed"; +import { useAuth } from "~/providers/UnifiedAuthProvider"; +import { quickAuthAtom } from "~/utils/atoms"; +import { useQueryArbitrary, useQueryIdentity } from "~/utils/useQuery"; + +export const Route = createFileRoute("/profile/$did/feed/$rkey")({ + component: FeedRoute, +}); + +function FeedRoute() { + const { did, rkey } = Route.useParams(); + const { agent, status } = useAuth(); + const { data: identitydata } = useQueryIdentity(did); + const { data: identity } = useQueryIdentity(agent?.did); + const uri = `at://${identitydata?.did || did}/app.bsky.feed.generator/${rkey}`; + const aturi = new AtUri(uri); + const { data: feeddata } = useQueryArbitrary(uri); + + const [quickAuth, setQuickAuth] = useAtom(quickAuthAtom); + const isAuthRestoring = quickAuth ? status === "loading" : false; + + const authed = status === "signedIn"; + + const feedServiceDid = !isAuthRestoring + ? ((feeddata?.value as any)?.did as string | undefined) + : undefined; + + // const { + // data: feedData, + // isLoading: isFeedLoading, + // error: feedError, + // } = useQueryFeedSkeleton({ + // feedUri: selectedFeed!, + // agent: agent ?? undefined, + // isAuthed: authed ?? false, + // pdsUrl: identity?.pds, + // feedServiceDid: feedServiceDid, + // }); + + // const feed = feedData?.feed || []; + + const isReadyForAuthedFeed = + !isAuthRestoring && authed && agent && identity?.pds && feedServiceDid; + const isReadyForUnauthedFeed = !isAuthRestoring && !authed; + + const feed: ATPAPI.AppBskyFeedGenerator.Record | undefined = feeddata?.value; + + const web = feedServiceDid?.replace(/^did:web:/, "") || ""; + + return ( + <> +
{ + if (window.history.length > 1) { + window.history.back(); + } else { + window.location.assign("/"); + } + }} + /> + + {isAuthRestoring || + (authed && (!identity?.pds || !feedServiceDid) && ( +
+ Preparing your feed... +
+ ))} + + {!isAuthRestoring && (isReadyForAuthedFeed || isReadyForUnauthedFeed) ? ( + + ) : ( +
Loading.......
+ )} + + ); +} diff --git a/src/routes/profile.$did/index.tsx b/src/routes/profile.$did/index.tsx index e6c2c19..a9c1c8c 100644 --- a/src/routes/profile.$did/index.tsx +++ b/src/routes/profile.$did/index.tsx @@ -1,7 +1,7 @@ import { RichText } from "@atproto/api"; import * as ATPAPI from "@atproto/api"; import { useQueryClient } from "@tanstack/react-query"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { useAtom } from "jotai"; import React, { type ReactNode, useEffect, useState } from "react"; @@ -24,6 +24,7 @@ import { } from "~/utils/followState"; import { useInfiniteQueryAuthorFeed, + useQueryArbitrary, useQueryConstellation, useQueryIdentity, useQueryProfile, @@ -403,16 +404,41 @@ function FeedsTab({ did }: { did: string }) { ); } -function FeedItemRender({ +export function FeedItemRenderAturiLoader({ + aturi, + listmode, + disableBottomBorder, +}: { + aturi: string; + listmode?: boolean; + disableBottomBorder?: boolean; +}) { + const { data: record } = useQueryArbitrary(aturi); + + if (!record) return; + return ( + + ); +} + +export function FeedItemRender({ feed, - listmode + listmode, + disableBottomBorder, }: { - feed: { uri: string; cid: string; value: ATPAPI.AppBskyFeedGenerator.Record }; + feed: { uri: string; cid: string; value: any }; listmode?: boolean; + disableBottomBorder?: boolean; }) { - const name = listmode ? feed.value?.name as string : feed.value?.displayName as string; + const name = listmode + ? (feed.value?.name as string) + : (feed.value?.displayName as string); const aturi = new ATPAPI.AtUri(feed.uri); - const {data: identity} = useQueryIdentity(aturi.host); + const { data: identity } = useQueryIdentity(aturi.host); const resolvedDid = identity?.did; const [imgcdn] = useAtom(imgCDNAtom); @@ -422,34 +448,50 @@ function FeedItemRender({ return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`; } + const { data: likes } = useQueryConstellation( // @ts-expect-error overloads sucks - const {data: likes} = useQueryConstellation(!listmode ? { - target: feed.uri, - method: "/links/count", - collection: "app.bsky.feed.like", - path: ".subject.uri" - } : undefined) + !listmode + ? { + target: feed.uri, + method: "/links/count", + collection: "app.bsky.feed.like", + path: ".subject.uri", + } + : undefined + ); return ( -
+
- +
{name} - {feed.value.did || aturi.rkey} + + {feed.value.did || aturi.rkey} +
{/*
*/}
{feed.value?.description} - {!listmode && (Liked by {(likes as unknown as any)?.total as number || 0} users)} -
+ {!listmode && ( + + Liked by {((likes as unknown as any)?.total as number) || 0} users + + )} + ); } - function ListsTab({ did }: { did: string }) { useReusableTabScrollRestore(`Profile` + did); const { @@ -487,7 +529,9 @@ function ListsTab({ did }: { did: string }) { if (!feed || !feed?.value) return; const feedGenRecord = feed.value as unknown as ATPAPI.AppBskyFeedGenerator.Record; - return ; + return ( + + ); })}
diff --git a/src/utils/useQuery.ts b/src/utils/useQuery.ts index eb0836d..5f90e63 100644 --- a/src/utils/useQuery.ts +++ b/src/utils/useQuery.ts @@ -573,8 +573,10 @@ export function constructInfiniteFeedSkeletonQuery(options: { isAuthed: boolean; pdsUrl?: string; feedServiceDid?: string; + // todo the hell is a unauthedfeedurl + unauthedfeedurl?: string; }) { - const { feedUri, agent, isAuthed, pdsUrl, feedServiceDid } = options; + const { feedUri, agent, isAuthed, pdsUrl, feedServiceDid, unauthedfeedurl } = options; return queryOptions({ queryKey: ["feedSkeleton", feedUri, { isAuthed, did: agent?.did }], @@ -582,7 +584,7 @@ export function constructInfiniteFeedSkeletonQuery(options: { queryFn: async ({ pageParam }: QueryFunctionContext): Promise => { const cursorParam = pageParam ? `&cursor=${pageParam}` : ""; - if (isAuthed) { + if (isAuthed && !unauthedfeedurl) { if (!agent || !pdsUrl || !feedServiceDid) { throw new Error("Missing required info for authenticated feed fetch."); } @@ -597,7 +599,7 @@ export function constructInfiniteFeedSkeletonQuery(options: { if (!res.ok) throw new Error(`Authenticated feed fetch failed: ${res.statusText}`); return (await res.json()) as FeedSkeletonPage; } else { - const url = `https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`; + const url = `https://${unauthedfeedurl ? unauthedfeedurl : "discover.bsky.app"}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`; const res = await fetch(url); if (!res.ok) throw new Error(`Public feed fetch failed: ${res.statusText}`); return (await res.json()) as FeedSkeletonPage; @@ -612,6 +614,7 @@ export function useInfiniteQueryFeedSkeleton(options: { isAuthed: boolean; pdsUrl?: string; feedServiceDid?: string; + unauthedfeedurl?: string; }) { const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery(options); @@ -622,7 +625,7 @@ export function useInfiniteQueryFeedSkeleton(options: { getNextPageParam: (lastPage) => lastPage.cursor as null | undefined, staleTime: Infinity, refetchOnWindowFocus: false, - enabled: !!options.feedUri && (options.isAuthed ? !!options.agent && !!options.pdsUrl && !!options.feedServiceDid : true), + enabled: !!options.feedUri && (options.isAuthed ? (!!options.agent && !!options.pdsUrl || !!options.unauthedfeedurl) && !!options.feedServiceDid : true), }), queryKey: queryKey}; }