- {props.quotes.length === 0 ? (
+ {props.quotesAndMentions.length === 0 ? (
- {props.quotes.map((q, index) => {
- let pv = q.bsky_posts?.post_view as unknown as PostView;
+ {/* Quotes with links (quoted content) */}
+ {quotesWithLinks.map((q, index) => {
+ const pv = postViewMap.get(q.uri);
+ if (!pv || !q.link) return null;
const url = new URL(q.link);
const quoteParam = url.pathname.split("/l-quote/")[1];
if (!quoteParam) return null;
const quotePosition = decodeQuotePosition(quoteParam);
if (!quotePosition) return null;
return (
-
+
);
})}
+
+ {/* Direct post mentions (without quoted content) */}
+ {directMentions.length > 0 && (
+
+
Post Mentions
+
+ {directMentions.map((q, index) => {
+ const pv = postViewMap.get(q.uri);
+ if (!pv) return null;
+ return (
+
+ );
+ })}
+
+
+ )}
)}
@@ -154,7 +203,7 @@ export const QuoteContent = (props: {
);
};
-const BskyPost = (props: {
+export const BskyPost = (props: {
rkey: string;
content: string;
user: string;
diff --git a/app/lish/[did]/[publication]/[rkey]/Interactions/getBlueskyMentions.ts b/app/lish/[did]/[publication]/[rkey]/Interactions/getBlueskyMentions.ts
new file mode 100644
index 00000000..5d35e69d
--- /dev/null
+++ b/app/lish/[did]/[publication]/[rkey]/Interactions/getBlueskyMentions.ts
@@ -0,0 +1,80 @@
+"use server";
+
+import { AtUri, Agent, lexToJson } from "@atproto/api";
+import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs";
+
+type ConstellationResponse = {
+ records: { did: string; collection: string; rkey: string }[];
+};
+
+const headers = {
+ "Content-type": "application/json",
+ "user-agent": "leaflet.pub",
+};
+
+// Fetch constellation backlinks without hydrating with Bluesky post data
+export async function getConstellationBacklinks(
+ url: string,
+): Promise<{ uri: string }[]> {
+ let baseURL = `https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=${encodeURIComponent(url)}`;
+ let externalEmbeds = new URL(
+ `${baseURL}&source=${encodeURIComponent("app.bsky.feed.post:embed.external.uri")}`,
+ );
+ let linkFacets = new URL(
+ `${baseURL}&source=${encodeURIComponent("app.bsky.feed.post:facets[].features[app.bsky.richtext.facet#link].uri")}`,
+ );
+
+ let [links, embeds] = (await Promise.all([
+ fetch(linkFacets, { headers, next: { revalidate: 3600 } }).then((req) =>
+ req.json(),
+ ),
+ fetch(externalEmbeds, { headers, next: { revalidate: 3600 } }).then((req) =>
+ req.json(),
+ ),
+ ])) as ConstellationResponse[];
+
+ let uris = [...links.records, ...embeds.records].map((i) =>
+ AtUri.make(i.did, i.collection, i.rkey).toString(),
+ );
+
+ return uris.map((uri) => ({ uri }));
+}
+
+// Hydrate Bluesky URIs with post data
+export async function hydrateBlueskyPosts(uris: string[]): Promise
{
+ if (uris.length === 0) return [];
+
+ let agent = new Agent({
+ service: "https://public.api.bsky.app",
+ fetch: (...args) =>
+ fetch(args[0], {
+ ...args[1],
+ next: { revalidate: 3600 },
+ }),
+ });
+
+ // Process URIs in batches of 25
+ let allPostRequests = [];
+ for (let i = 0; i < uris.length; i += 25) {
+ let batch = uris.slice(i, i + 25);
+ let batchPosts = agent.getPosts(
+ {
+ uris: batch,
+ },
+ { headers: {} },
+ );
+ allPostRequests.push(batchPosts);
+ }
+ let allPosts = (await Promise.all(allPostRequests)).flatMap(
+ (r) => r.data.posts,
+ );
+
+ return lexToJson(allPosts) as PostView[];
+}
+
+// Legacy function - kept for backwards compatibility if needed
+export async function getMentions(url: string) {
+ let backlinks = await getConstellationBacklinks(url);
+ let uris = backlinks.map((b) => b.uri);
+ return hydrateBlueskyPosts(uris);
+}
diff --git a/app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx b/app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
index b59daecd..ebea43ff 100644
--- a/app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
+++ b/app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
@@ -5,13 +5,16 @@ import {
PubLeafletPublication,
} from "lexicons/api";
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
-import { Interactions } from "../Interactions/Interactions";
+import {
+ Interactions,
+ getQuoteCount,
+ getCommentCount,
+} from "../Interactions/Interactions";
import { PostPageData } from "../getPostPageData";
import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
import { useIdentityData } from "components/IdentityProvider";
import { EditTiny } from "components/Icons/EditTiny";
import { SpeedyLink } from "components/SpeedyLink";
-import { decodeQuotePosition } from "../quotePosition";
import { useLocalizedDate } from "src/hooks/useLocalizedDate";
export function PostHeader(props: {
@@ -95,20 +98,8 @@ export function PostHeader(props: {
{
- const url = new URL(q.link);
- const quoteParam = url.pathname.split("/l-quote/")[1];
- if (!quoteParam) return null;
- const quotePosition = decodeQuotePosition(quoteParam);
- return !quotePosition?.pageId;
- }).length
- }
- commentsCount={
- document.comments_on_documents.filter(
- (c) => !(c.record as PubLeafletComment.Record)?.onPage,
- ).length
- }
+ quotesCount={getQuoteCount(document) || 0}
+ commentsCount={getCommentCount(document) || 0}
/>