From 856e0c8fa0829bf8b382775d1483f96233cc758e Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 10 Feb 2026 13:01:14 +0400 Subject: [PATCH] fix post links to standard site blogs --- .../BskyPostEmbedNotification.tsx | 7 +-- .../CommentMentionNotification.tsx | 10 ++--- .../notifications/CommentNotication.tsx | 10 ++--- .../notifications/MentionNotification.tsx | 9 +--- .../notifications/QuoteNotification.tsx | 8 +--- .../notifications/RecommendNotification.tsx | 7 +-- .../notifications/ReplyNotification.tsx | 10 ++--- .../actions/ShareOptions/index.tsx | 16 ++++--- .../[command]/search_publication_documents.ts | 12 ++--- .../[publication]/[rkey]/getPostPageData.ts | 12 ++--- .../dashboard/PublishedPostsLists.tsx | 7 +-- app/lish/[did]/[publication]/generateFeed.ts | 6 ++- app/lish/[did]/[publication]/page.tsx | 7 +-- app/lish/createPub/getPublicationURL.ts | 45 +++++++++++++++++++ components/PageSWRDataProvider.tsx | 28 +++++++++--- components/PostListing.tsx | 5 +-- 16 files changed, 123 insertions(+), 76 deletions(-) diff --git a/app/(home-pages)/notifications/BskyPostEmbedNotification.tsx b/app/(home-pages)/notifications/BskyPostEmbedNotification.tsx index 2b433771..66cf0018 100644 --- a/app/(home-pages)/notifications/BskyPostEmbedNotification.tsx +++ b/app/(home-pages)/notifications/BskyPostEmbedNotification.tsx @@ -2,6 +2,7 @@ import { BlueskyTiny } from "components/Icons/BlueskyTiny"; import { ContentLayout, Notification } from "./Notification"; import { HydratedBskyPostEmbedNotification } from "src/notifications"; import { AtUri } from "@atproto/api"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export const BskyPostEmbedNotification = ( props: HydratedBskyPostEmbedNotification, @@ -11,11 +12,7 @@ export const BskyPostEmbedNotification = ( if (!docRecord) return null; - const docUri = new AtUri(props.document.uri); - const rkey = docUri.rkey; - const did = docUri.host; - - const href = pubRecord ? `${pubRecord.url}/${rkey}` : `/p/${did}/${rkey}`; + const href = getDocumentURL(docRecord, props.document.uri, pubRecord); const embedder = props.documentCreatorHandle ? `@${props.documentCreatorHandle}` diff --git a/app/(home-pages)/notifications/CommentMentionNotification.tsx b/app/(home-pages)/notifications/CommentMentionNotification.tsx index 10917d61..3bf1e63d 100644 --- a/app/(home-pages)/notifications/CommentMentionNotification.tsx +++ b/app/(home-pages)/notifications/CommentMentionNotification.tsx @@ -8,6 +8,7 @@ import { Notification, } from "./Notification"; import { AtUri } from "@atproto/api"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export const CommentMentionNotification = ( props: HydratedCommentMentionNotification, @@ -19,13 +20,10 @@ export const CommentMentionNotification = ( const profileRecord = props.commentData.bsky_profiles ?.record as AppBskyActorProfile.Record; const pubRecord = props.normalizedPublication; - const docUri = new AtUri(props.commentData.documents?.uri!); - const rkey = docUri.rkey; - const did = docUri.host; - const href = pubRecord - ? `${pubRecord.url}/${rkey}?interactionDrawer=comments` - : `/p/${did}/${rkey}?interactionDrawer=comments`; + const href = + getDocumentURL(docRecord, props.commentData.documents?.uri!, pubRecord) + + "?interactionDrawer=comments"; const commenter = props.commenterHandle ? `@${props.commenterHandle}` diff --git a/app/(home-pages)/notifications/CommentNotication.tsx b/app/(home-pages)/notifications/CommentNotication.tsx index 500d2783..fde0e160 100644 --- a/app/(home-pages)/notifications/CommentNotication.tsx +++ b/app/(home-pages)/notifications/CommentNotication.tsx @@ -10,6 +10,7 @@ import { Notification, } from "./Notification"; import { AtUri } from "@atproto/api"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export const CommentNotification = (props: HydratedCommentNotification) => { const docRecord = props.normalizedDocument; @@ -24,13 +25,10 @@ export const CommentNotification = (props: HydratedCommentNotification) => { props.commentData.bsky_profiles?.handle || "Someone"; const pubRecord = props.normalizedPublication; - const docUri = new AtUri(props.commentData.documents?.uri!); - const rkey = docUri.rkey; - const did = docUri.host; - const href = pubRecord - ? `${pubRecord.url}/${rkey}?interactionDrawer=comments` - : `/p/${did}/${rkey}?interactionDrawer=comments`; + const href = + getDocumentURL(docRecord, props.commentData.documents?.uri!, pubRecord) + + "?interactionDrawer=comments"; return ( { const docRecord = props.normalizedDocument; @@ -9,13 +10,7 @@ export const MentionNotification = (props: HydratedMentionNotification) => { if (!docRecord) return null; - const docUri = new AtUri(props.document.uri); - const rkey = docUri.rkey; - const did = docUri.host; - - const href = pubRecord - ? `${pubRecord.url}/${rkey}` - : `/p/${did}/${rkey}`; + const href = getDocumentURL(docRecord, props.document.uri, pubRecord); let actionText: React.ReactNode; let mentionedItemName: string | undefined; diff --git a/app/(home-pages)/notifications/QuoteNotification.tsx b/app/(home-pages)/notifications/QuoteNotification.tsx index d6849205..6c7b3f16 100644 --- a/app/(home-pages)/notifications/QuoteNotification.tsx +++ b/app/(home-pages)/notifications/QuoteNotification.tsx @@ -3,6 +3,7 @@ import { ContentLayout, Notification } from "./Notification"; import { HydratedQuoteNotification } from "src/notifications"; import { AtUri } from "@atproto/api"; import { Avatar } from "components/Avatar"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export const QuoteNotification = (props: HydratedQuoteNotification) => { const postView = props.bskyPost.post_view as any; @@ -13,14 +14,9 @@ export const QuoteNotification = (props: HydratedQuoteNotification) => { if (!docRecord) return null; - const docUri = new AtUri(props.document.uri); - const rkey = docUri.rkey; - const did = docUri.host; const postText = postView.record?.text || ""; - const href = pubRecord - ? `${pubRecord.url}/${rkey}` - : `/p/${did}/${rkey}`; + const href = getDocumentURL(docRecord, props.document.uri, pubRecord); return ( { const docRecord = props.normalizedDocument; @@ -32,14 +33,11 @@ export const ReplyNotification = (props: HydratedCommentNotification) => { props.parentData?.bsky_profiles?.handle || "Someone"; - const docUri = new AtUri(props.commentData.documents?.uri!); - const rkey = docUri.rkey; - const did = docUri.host; const pubRecord = props.normalizedPublication; - const href = pubRecord - ? `${pubRecord.url}/${rkey}?interactionDrawer=comments` - : `/p/${did}/${rkey}?interactionDrawer=comments`; + const href = + getDocumentURL(docRecord, props.commentData.documents?.uri!, pubRecord) + + "?interactionDrawer=comments"; return ( (null); useEffect(() => { diff --git a/app/api/rpc/[command]/search_publication_documents.ts b/app/api/rpc/[command]/search_publication_documents.ts index a58f6867..a52be5f6 100644 --- a/app/api/rpc/[command]/search_publication_documents.ts +++ b/app/api/rpc/[command]/search_publication_documents.ts @@ -2,7 +2,8 @@ import { AtUri } from "@atproto/api"; import { z } from "zod"; import { makeRoute } from "../lib"; import type { Env } from "./route"; -import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; +import { normalizeDocumentRecord } from "src/utils/normalizeRecords"; export type SearchPublicationDocumentsReturnType = Awaited< ReturnType<(typeof search_publication_documents)["handler"]> @@ -37,13 +38,14 @@ export const search_publication_documents = makeRoute({ } const result = documents.map((d) => { - const docUri = new AtUri(d.documents.uri); - const pubUrl = getPublicationURL(d.publications); + const normalizedDoc = normalizeDocumentRecord(d.documents.data, d.documents.uri); return { uri: d.documents.uri, - title: (d.documents.data as { title?: string })?.title || "Untitled", - url: `${pubUrl}/${docUri.rkey}`, + title: normalizedDoc?.title || (d.documents.data as { title?: string })?.title || "Untitled", + url: normalizedDoc + ? getDocumentURL(normalizedDoc, d.documents.uri, d.publications) + : `${d.documents.uri}`, }; }); diff --git a/app/lish/[did]/[publication]/[rkey]/getPostPageData.ts b/app/lish/[did]/[publication]/[rkey]/getPostPageData.ts index 7f16418a..49eb500b 100644 --- a/app/lish/[did]/[publication]/[rkey]/getPostPageData.ts +++ b/app/lish/[did]/[publication]/[rkey]/getPostPageData.ts @@ -8,6 +8,7 @@ import { } from "src/utils/normalizeRecords"; import { PubLeafletPublication, SiteStandardPublication } from "lexicons/api"; import { documentUriFilter } from "src/utils/uriHelpers"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export async function getPostPageData(did: string, rkey: string) { let { data: documents } = await supabaseServerClient @@ -46,11 +47,12 @@ export async function getPostPageData(did: string, rkey: string) { ); // Fetch constellation backlinks for mentions - let aturi = new AtUri(document.uri); - const postUrl = normalizedPublication - ? `${normalizedPublication.url}/${aturi.rkey}` - : `https://leaflet.pub/p/${aturi.host}/${aturi.rkey}`; - const constellationBacklinks = await getConstellationBacklinks(postUrl); + const postUrl = getDocumentURL(normalizedDocument, document.uri, normalizedPublication); + // Constellation needs an absolute URL + const absolutePostUrl = postUrl.startsWith("/") + ? `https://leaflet.pub${postUrl}` + : postUrl; + const constellationBacklinks = await getConstellationBacklinks(absolutePostUrl); // Deduplicate constellation backlinks (same post could appear in both links and embeds) const uniqueBacklinks = Array.from( diff --git a/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx b/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx index 29b4b8a8..a3e25db9 100644 --- a/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx +++ b/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx @@ -9,7 +9,7 @@ import { } from "./PublicationSWRProvider"; import { Fragment } from "react"; import { useParams } from "next/navigation"; -import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; +import { getPublicationURL, getDocumentURL } from "app/lish/createPub/getPublicationURL"; import { SpeedyLink } from "components/SpeedyLink"; import { InteractionPreview } from "components/InteractionsPreview"; import { useLocalizedDate } from "src/hooks/useLocalizedDate"; @@ -71,6 +71,7 @@ function PublishedPostItem(props: { const leaflet = publication.leaflets_in_publications.find( (l) => l.doc === doc.uri, ); + const docUrl = getDocumentURL(doc.record, doc.uri, publication); return ( @@ -87,7 +88,7 @@ function PublishedPostItem(props: {

{doc.record.title} @@ -144,7 +145,7 @@ function PublishedPostItem(props: { showComments={pubRecord?.preferences?.showComments !== false} showMentions={pubRecord?.preferences?.showMentions !== false} showRecommends={pubRecord?.preferences?.showRecommends !== false} - postUrl={`${getPublicationURL(publication)}/${uri.rkey}`} + postUrl={docUrl} /> diff --git a/app/lish/[did]/[publication]/generateFeed.ts b/app/lish/[did]/[publication]/generateFeed.ts index e12aa75f..383f9461 100644 --- a/app/lish/[did]/[publication]/generateFeed.ts +++ b/app/lish/[did]/[publication]/generateFeed.ts @@ -11,6 +11,7 @@ import { hasLeafletContent, } from "src/utils/normalizeRecords"; import { publicationNameOrUriFilter } from "src/utils/uriHelpers"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export async function generateFeed( did: string, @@ -84,12 +85,13 @@ export async function generateFeed( } } + const docUrl = getDocumentURL(record, doc.documents.uri, pubRecord); feed.addItem({ title: record.title, description: record.description, date: record.publishedAt ? new Date(record.publishedAt) : new Date(), - id: `${pubRecord.url}/${rkey}`, - link: `${pubRecord.url}/${rkey}`, + id: docUrl, + link: docUrl, content: chunks.join(""), }); }), diff --git a/app/lish/[did]/[publication]/page.tsx b/app/lish/[did]/[publication]/page.tsx index 457f38b2..6d6e2e31 100644 --- a/app/lish/[did]/[publication]/page.tsx +++ b/app/lish/[did]/[publication]/page.tsx @@ -1,6 +1,6 @@ import { supabaseServerClient } from "supabase/serverClient"; import { AtUri } from "@atproto/syntax"; -import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; +import { getPublicationURL, getDocumentURL } from "app/lish/createPub/getPublicationURL"; import { BskyAgent } from "@atproto/api"; import { publicationNameOrUriFilter } from "src/utils/uriHelpers"; import { SubscribeWithBluesky } from "app/lish/Subscribe"; @@ -135,11 +135,12 @@ export default async function Publication(props: { doc.documents.recommends_on_documents?.[0]?.count || 0; let tags = doc_record.tags || []; + const docUrl = getDocumentURL(doc_record, doc.documents.uri, publication); return (

{doc_record.title}

@@ -168,7 +169,7 @@ export default async function Publication(props: { recommendsCount={recommends} documentUri={doc.documents.uri} tags={tags} - postUrl={`${getPublicationURL(publication)}/${uri.rkey}`} + postUrl={docUrl} showComments={ record?.preferences?.showComments !== false } diff --git a/app/lish/createPub/getPublicationURL.ts b/app/lish/createPub/getPublicationURL.ts index 613bef1b..3f864599 100644 --- a/app/lish/createPub/getPublicationURL.ts +++ b/app/lish/createPub/getPublicationURL.ts @@ -5,6 +5,8 @@ import { Json } from "supabase/database.types"; import { normalizePublicationRecord, isLeafletPublication, + hasLeafletContent, + type NormalizedDocument, type NormalizedPublication, } from "src/utils/normalizeRecords"; @@ -44,3 +46,46 @@ export function getBasePublicationURL(pub: PublicationInput): string { const name = aturi.rkey || normalized?.name; return `/lish/${aturi.host}/${encodeURIComponent(name || "")}`; } + +/** + * Gets the full URL for a document. + * Always appends the document's path property. + * For non-leaflet documents (content.$type !== "pub.leaflet.content"), + * always uses the full publication site URL, not internal /lish/ URLs. + */ +export function getDocumentURL( + doc: NormalizedDocument, + docUri: string, + publication?: PublicationInput | NormalizedPublication | null, +): string { + const path = doc.path || "/" + new AtUri(docUri).rkey; + const aturi = new AtUri(docUri); + + const isNormalized = + !!publication && + (publication as NormalizedPublication).$type === "site.standard.publication"; + const normPub = isNormalized + ? (publication as NormalizedPublication) + : publication + ? normalizePublicationRecord((publication as PublicationInput).record) + : null; + const pubInput = isNormalized ? null : (publication as PublicationInput | null); + + // Non-leaflet documents always use the full publication site URL + if (doc.content && !hasLeafletContent(doc) && normPub?.url) { + return normPub.url + path; + } + + // For leaflet documents, use getPublicationURL (may return /lish/ internal paths) + if (pubInput) { + return getPublicationURL(pubInput) + path; + } + + // When we only have a normalized publication, use its URL directly + if (normPub?.url) { + return normPub.url + path; + } + + // Standalone document fallback + return `/p/${aturi.host}${path}`; +} diff --git a/components/PageSWRDataProvider.tsx b/components/PageSWRDataProvider.tsx index 227c17e9..189a4e43 100644 --- a/components/PageSWRDataProvider.tsx +++ b/components/PageSWRDataProvider.tsx @@ -8,7 +8,7 @@ import { getPollData } from "actions/pollActions"; import type { GetLeafletDataReturnType } from "app/api/rpc/[command]/get_leaflet_data"; import { createContext, useContext, useMemo } from "react"; import { getPublicationMetadataFromLeafletData } from "src/utils/getPublicationMetadataFromLeafletData"; -import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; +import { getPublicationURL, getDocumentURL } from "app/lish/createPub/getPublicationURL"; import { AtUri } from "@atproto/syntax"; import { normalizeDocumentRecord, @@ -119,13 +119,27 @@ export function useLeafletPublicationStatus() { // Compute the full post URL for sharing let postShareLink: string | undefined; if (publishedInPublication?.publications && publishedInPublication.documents) { - // Published in a publication - use publication URL + document rkey - const docUri = new AtUri(publishedInPublication.documents.uri); - postShareLink = `${getPublicationURL(publishedInPublication.publications)}/${docUri.rkey}`; + const normalizedDoc = normalizeDocumentRecord( + publishedInPublication.documents.data, + publishedInPublication.documents.uri, + ); + if (normalizedDoc) { + postShareLink = getDocumentURL( + normalizedDoc, + publishedInPublication.documents.uri, + publishedInPublication.publications, + ); + } } else if (publishedStandalone?.document) { - // Standalone published post - use /p/{did}/{rkey} format - const docUri = new AtUri(publishedStandalone.document); - postShareLink = `/p/${docUri.host}/${docUri.rkey}`; + const normalizedDoc = publishedStandalone.documents + ? normalizeDocumentRecord(publishedStandalone.documents.data, publishedStandalone.document) + : null; + if (normalizedDoc) { + postShareLink = getDocumentURL(normalizedDoc, publishedStandalone.document); + } else { + const docUri = new AtUri(publishedStandalone.document); + postShareLink = `/p/${docUri.host}/${docUri.rkey}`; + } } return { diff --git a/components/PostListing.tsx b/components/PostListing.tsx index 32bd9bd4..cee5cd5a 100644 --- a/components/PostListing.tsx +++ b/components/PostListing.tsx @@ -18,6 +18,7 @@ import Link from "next/link"; import { InteractionPreview } from "./InteractionsPreview"; import { useLocalizedDate } from "src/hooks/useLocalizedDate"; import { mergePreferences } from "src/utils/mergePreferences"; +import { getDocumentURL } from "app/lish/createPub/getPublicationURL"; export const PostListing = (props: Post) => { let pubRecord = props.publication?.pubRecord as @@ -60,9 +61,7 @@ export const PostListing = (props: Post) => { let tags = (postRecord?.tags as string[] | undefined) || []; // For standalone posts, link directly to the document - let postHref = props.publication - ? `${props.publication.href}/${postUri.rkey}` - : `/p/${postUri.host}/${postUri.rkey}`; + let postHref = getDocumentURL(postRecord, props.documents.uri, pubRecord); return ( -- 2.51.2