From 01f52ae21e7ff6f9a6fb111ece227515f8a1b7f7 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Thu, 19 Feb 2026 18:48:47 -0500 Subject: [PATCH] support linking to specific bsky clients --- actions/publishToPublication.ts | 2 + .../[rkey]/Blocks/PublishBskyPostBlock.tsx | 20 ++-------- .../[publication]/[rkey]/BskyPostContent.tsx | 9 +++-- .../[publication]/[rkey]/PostContent.tsx | 1 + components/Blocks/BlueskyPostBlock/index.tsx | 37 ++----------------- lexicons/api/lexicons.ts | 3 ++ .../api/types/pub/leaflet/blocks/bskyPost.ts | 1 + lexicons/pub/leaflet/blocks/bskyPost.json | 3 ++ lexicons/src/blocks.ts | 1 + src/replicache/attributes.ts | 4 ++ src/utils/addLinkBlock.ts | 37 ++++++++++++------- 11 files changed, 52 insertions(+), 66 deletions(-) diff --git a/actions/publishToPublication.ts b/actions/publishToPublication.ts index 70aa3d9d..cbd43c55 100644 --- a/actions/publishToPublication.ts +++ b/actions/publishToPublication.ts @@ -512,12 +512,14 @@ async function processBlocksToPages( if (b.type === "bluesky-post") { let [post] = scan.eav(b.value, "block/bluesky-post"); if (!post || !post.data.value.post) return; + let [hostFact] = scan.eav(b.value, "bluesky-post/host"); let block: $Typed = { $type: ids.PubLeafletBlocksBskyPost, postRef: { uri: post.data.value.post.uri, cid: post.data.value.post.cid, }, + clientHost: hostFact?.data.value, }; return block; } diff --git a/app/lish/[did]/[publication]/[rkey]/Blocks/PublishBskyPostBlock.tsx b/app/lish/[did]/[publication]/[rkey]/Blocks/PublishBskyPostBlock.tsx index 99cbc481..10b5d210 100644 --- a/app/lish/[did]/[publication]/[rkey]/Blocks/PublishBskyPostBlock.tsx +++ b/app/lish/[did]/[publication]/[rkey]/Blocks/PublishBskyPostBlock.tsx @@ -1,5 +1,5 @@ import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; -import { AppBskyFeedDefs, AppBskyFeedPost } from "@atproto/api"; +import { AppBskyFeedDefs } from "@atproto/api"; import { PostNotAvailable } from "components/Blocks/BlueskyPostBlock/BlueskyEmbed"; import { BskyPostContent } from "../BskyPostContent"; @@ -7,6 +7,7 @@ export const PubBlueskyPostBlock = (props: { post: PostView; className: string; pageId?: string; + clientHost?: string; }) => { let post = props.post; @@ -21,24 +22,8 @@ export const PubBlueskyPostBlock = (props: { ); case AppBskyFeedDefs.validatePostView(post).success: - let record = post.record as AppBskyFeedDefs.PostView["record"]; - - // silliness to get the text and timestamp from the record with proper types - let timestamp: string | undefined = undefined; - if (AppBskyFeedPost.isRecord(record)) { - timestamp = (record as AppBskyFeedPost.Record).createdAt; - } - - //getting the url to the post - let postId = post.uri.split("/")[4]; let postView = post as PostView; - let url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; - - const parent = props.pageId - ? { type: "doc" as const, id: props.pageId } - : undefined; - return ( ); } diff --git a/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx b/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx index 6478687e..850c6168 100644 --- a/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx +++ b/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx @@ -28,6 +28,7 @@ export function BskyPostContent(props: { quoteEnabled?: boolean; replyEnabled?: boolean; replyOnClick?: (e: React.MouseEvent) => void; + clientHost?: string; }) { const { post, @@ -39,11 +40,12 @@ export function BskyPostContent(props: { quoteEnabled, replyEnabled, replyOnClick, + clientHost = "bsky.app", } = props; const record = post.record as AppBskyFeedPost.Record; const postId = post.uri.split("/")[4]; - const url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; + const url = `https://${clientHost}/profile/${post.author.handle}/post/${postId}`; return (
@@ -138,12 +140,13 @@ export function CompactBskyPostContent(props: { quoteEnabled?: boolean; replyEnabled?: boolean; replyOnClick?: (e: React.MouseEvent) => void; + clientHost?: string; }) { - const { post, parent, quoteEnabled, replyEnabled, replyOnClick } = props; + const { post, parent, quoteEnabled, replyEnabled, replyOnClick, clientHost = "bsky.app" } = props; const record = post.record as AppBskyFeedPost.Record; const postId = post.uri.split("/")[4]; - const url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; + const url = `https://${clientHost}/profile/${post.author.handle}/post/${postId}`; return (
diff --git a/app/lish/[did]/[publication]/[rkey]/PostContent.tsx b/app/lish/[did]/[publication]/[rkey]/PostContent.tsx index 37e7980b..47c27dc0 100644 --- a/app/lish/[did]/[publication]/[rkey]/PostContent.tsx +++ b/app/lish/[did]/[publication]/[rkey]/PostContent.tsx @@ -177,6 +177,7 @@ export let Block = ({ post={post} className={className} pageId={pageId} + clientHost={b.block.clientHost} /> ); } diff --git a/components/Blocks/BlueskyPostBlock/index.tsx b/components/Blocks/BlueskyPostBlock/index.tsx index 10353ad2..050a3c27 100644 --- a/components/Blocks/BlueskyPostBlock/index.tsx +++ b/components/Blocks/BlueskyPostBlock/index.tsx @@ -1,18 +1,14 @@ import { useEntitySetContext } from "components/EntitySetProvider"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useEntity } from "src/replicache"; import { useUIState } from "src/useUIState"; import { BlockProps, BlockLayout } from "../Block"; import { elementId } from "src/utils/elementId"; import { focusBlock } from "src/utils/focusBlock"; -import { AppBskyFeedDefs, AppBskyFeedPost, RichText } from "@atproto/api"; +import { AppBskyFeedDefs } from "@atproto/api"; import { PostNotAvailable } from "./BlueskyEmbed"; import { BlueskyPostEmpty } from "./BlueskyEmpty"; -import { Separator } from "components/Layout"; -import { BlueskyTiny } from "components/Icons/BlueskyTiny"; -import { CommentTiny } from "components/Icons/CommentTiny"; -import { useLocalizedDate } from "src/hooks/useLocalizedDate"; import { BskyPostContent } from "app/lish/[did]/[publication]/[rkey]/BskyPostContent"; import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; @@ -22,6 +18,7 @@ export const BlueskyPostBlock = (props: BlockProps & { preview?: boolean }) => { s.selectedBlocks.find((b) => b.value === props.entityID), ); let post = useEntity(props.entityID, "block/bluesky-post")?.data.value; + let clientHost = useEntity(props.entityID, "bluesky-post/host")?.data.value; useEffect(() => { if (props.preview) return; @@ -64,22 +61,7 @@ export const BlueskyPostBlock = (props: BlockProps & { preview?: boolean }) => { ); case AppBskyFeedDefs.isThreadViewPost(post): - let record = post.post - .record as AppBskyFeedDefs.FeedViewPost["post"]["record"]; - let facets = record.facets; - - // silliness to get the text and timestamp from the record with proper types - let text: string | null = null; - let timestamp: string | undefined = undefined; - if (AppBskyFeedPost.isRecord(record)) { - text = (record as AppBskyFeedPost.Record).text; - timestamp = (record as AppBskyFeedPost.Record).createdAt; - } - - //getting the url to the post - let postId = post.post.uri.split("/")[4]; let postView = post.post as PostView; - let url = `https://bsky.app/profile/${post.post.author.handle}/post/${postId}`; return ( { showEmbed={true} avatarSize="large" className="text-sm text-secondary " + clientHost={clientHost} /> ); } }; - -function PostDate(props: { timestamp: string }) { - const formattedDate = useLocalizedDate(props.timestamp, { - month: "short", - day: "numeric", - year: "numeric", - hour: "numeric", - minute: "numeric", - hour12: true, - }); - return
{formattedDate}
; -} diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts index a4b2a81f..15de0b86 100644 --- a/lexicons/api/lexicons.ts +++ b/lexicons/api/lexicons.ts @@ -1048,6 +1048,9 @@ export const schemaDict = { type: 'ref', ref: 'lex:com.atproto.repo.strongRef', }, + clientHost: { + type: 'string', + }, }, }, }, diff --git a/lexicons/api/types/pub/leaflet/blocks/bskyPost.ts b/lexicons/api/types/pub/leaflet/blocks/bskyPost.ts index 5c371dcb..06217602 100644 --- a/lexicons/api/types/pub/leaflet/blocks/bskyPost.ts +++ b/lexicons/api/types/pub/leaflet/blocks/bskyPost.ts @@ -18,6 +18,7 @@ const id = 'pub.leaflet.blocks.bskyPost' export interface Main { $type?: 'pub.leaflet.blocks.bskyPost' postRef: ComAtprotoRepoStrongRef.Main + clientHost?: string } const hashMain = 'main' diff --git a/lexicons/pub/leaflet/blocks/bskyPost.json b/lexicons/pub/leaflet/blocks/bskyPost.json index a0fbbf05..1442c12a 100644 --- a/lexicons/pub/leaflet/blocks/bskyPost.json +++ b/lexicons/pub/leaflet/blocks/bskyPost.json @@ -11,6 +11,9 @@ "postRef": { "type": "ref", "ref": "com.atproto.repo.strongRef" + }, + "clientHost": { + "type": "string" } } } diff --git a/lexicons/src/blocks.ts b/lexicons/src/blocks.ts index dd3ff4ec..c7d54ccb 100644 --- a/lexicons/src/blocks.ts +++ b/lexicons/src/blocks.ts @@ -43,6 +43,7 @@ export const PubLeafletBlocksBskyPost: LexiconDoc = { required: ["postRef"], properties: { postRef: { type: "ref", ref: "com.atproto.repo.strongRef" }, + clientHost: { type: "string" }, }, }, }, diff --git a/src/replicache/attributes.ts b/src/replicache/attributes.ts index 4f00e8e9..5ab061b3 100644 --- a/src/replicache/attributes.ts +++ b/src/replicache/attributes.ts @@ -151,6 +151,10 @@ const BlueskyPostBlockAttributes = { type: "string", cardinality: "one", }, + "bluesky-post/host": { + type: "string", + cardinality: "one", + }, } as const; const ButtonBlockAttributes = { diff --git a/src/utils/addLinkBlock.ts b/src/utils/addLinkBlock.ts index e08a0f27..3fd79e01 100644 --- a/src/utils/addLinkBlock.ts +++ b/src/utils/addLinkBlock.ts @@ -144,6 +144,7 @@ export async function addBlueskyPostBlock( ) { //construct bsky post uri from url let urlParts = url?.split("/"); + let host = urlParts ? urlParts[2] : "bsky.app"; // "bsky.app", "blacksky.community", "witchsky.app", etc. let userDidOrHandle = urlParts ? urlParts[4] : ""; // "schlage.town" or "did:plc:jjsc5rflv3cpv6hgtqhn2dcm" let collection = "app.bsky.feed.post"; let postId = urlParts ? urlParts[6] : ""; @@ -152,20 +153,30 @@ export async function addBlueskyPostBlock( let post = await getBlueskyPost(uri); if (!post || post === undefined) return false; - await rep.mutate.assertFact({ - entity: entityID, - attribute: "block/type", - data: { type: "block-type-union", value: "bluesky-post" }, - }); - await rep?.mutate.assertFact({ - entity: entityID, - attribute: "block/bluesky-post", - data: { - type: "bluesky-post", - //TODO: this is a hack to get rid of a nested Array buffer which cannot be frozen, which replicache does on write. - value: JSON.parse(JSON.stringify(post.data.thread)), + await rep.mutate.assertFact([ + { + entity: entityID, + attribute: "block/type", + data: { type: "block-type-union", value: "bluesky-post" }, }, - }); + { + entity: entityID, + attribute: "block/bluesky-post", + data: { + type: "bluesky-post", + //TODO: this is a hack to get rid of a nested Array buffer which cannot be frozen, which replicache does on write. + value: JSON.parse(JSON.stringify(post.data.thread)), + }, + }, + { + entity: entityID, + attribute: "bluesky-post/host", + data: { + type: "string", + value: host, + }, + }, + ]); return true; } async function getBlueskyPost(uri: string) { -- 2.51.2