a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

support linking to specific bsky clients

+52 -66
+2
actions/publishToPublication.ts
··· 512 512 if (b.type === "bluesky-post") { 513 513 let [post] = scan.eav(b.value, "block/bluesky-post"); 514 514 if (!post || !post.data.value.post) return; 515 + let [hostFact] = scan.eav(b.value, "bluesky-post/host"); 515 516 let block: $Typed<PubLeafletBlocksBskyPost.Main> = { 516 517 $type: ids.PubLeafletBlocksBskyPost, 517 518 postRef: { 518 519 uri: post.data.value.post.uri, 519 520 cid: post.data.value.post.cid, 520 521 }, 522 + clientHost: hostFact?.data.value, 521 523 }; 522 524 return block; 523 525 }
+3 -17
app/lish/[did]/[publication]/[rkey]/Blocks/PublishBskyPostBlock.tsx
··· 1 1 import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; 2 - import { AppBskyFeedDefs, AppBskyFeedPost } from "@atproto/api"; 2 + import { AppBskyFeedDefs } from "@atproto/api"; 3 3 import { PostNotAvailable } from "components/Blocks/BlueskyPostBlock/BlueskyEmbed"; 4 4 import { BskyPostContent } from "../BskyPostContent"; 5 5 ··· 7 7 post: PostView; 8 8 className: string; 9 9 pageId?: string; 10 + clientHost?: string; 10 11 }) => { 11 12 let post = props.post; 12 13 ··· 21 22 ); 22 23 23 24 case AppBskyFeedDefs.validatePostView(post).success: 24 - let record = post.record as AppBskyFeedDefs.PostView["record"]; 25 - 26 - // silliness to get the text and timestamp from the record with proper types 27 - let timestamp: string | undefined = undefined; 28 - if (AppBskyFeedPost.isRecord(record)) { 29 - timestamp = (record as AppBskyFeedPost.Record).createdAt; 30 - } 31 - 32 - //getting the url to the post 33 - let postId = post.uri.split("/")[4]; 34 25 let postView = post as PostView; 35 - 36 - let url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; 37 - 38 - const parent = props.pageId 39 - ? { type: "doc" as const, id: props.pageId } 40 - : undefined; 41 26 42 27 return ( 43 28 <BskyPostContent ··· 49 34 quoteEnabled 50 35 replyEnabled 51 36 className="text-sm text-secondary block-border sm:px-3 sm:py-2 px-2 py-1 bg-bg-page mb-2 hover:border-accent-contrast!" 37 + clientHost={props.clientHost} 52 38 /> 53 39 ); 54 40 }
+6 -3
app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx
··· 28 28 quoteEnabled?: boolean; 29 29 replyEnabled?: boolean; 30 30 replyOnClick?: (e: React.MouseEvent) => void; 31 + clientHost?: string; 31 32 }) { 32 33 const { 33 34 post, ··· 39 40 quoteEnabled, 40 41 replyEnabled, 41 42 replyOnClick, 43 + clientHost = "bsky.app", 42 44 } = props; 43 45 44 46 const record = post.record as AppBskyFeedPost.Record; 45 47 const postId = post.uri.split("/")[4]; 46 - const url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; 48 + const url = `https://${clientHost}/profile/${post.author.handle}/post/${postId}`; 47 49 48 50 return ( 49 51 <div className={`bskyPost relative flex flex-col w-full `}> ··· 138 140 quoteEnabled?: boolean; 139 141 replyEnabled?: boolean; 140 142 replyOnClick?: (e: React.MouseEvent) => void; 143 + clientHost?: string; 141 144 }) { 142 - const { post, parent, quoteEnabled, replyEnabled, replyOnClick } = props; 145 + const { post, parent, quoteEnabled, replyEnabled, replyOnClick, clientHost = "bsky.app" } = props; 143 146 144 147 const record = post.record as AppBskyFeedPost.Record; 145 148 const postId = post.uri.split("/")[4]; 146 - const url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; 149 + const url = `https://${clientHost}/profile/${post.author.handle}/post/${postId}`; 147 150 148 151 return ( 149 152 <div className="bskyPost relative flex flex-col w-full">
+1
app/lish/[did]/[publication]/[rkey]/PostContent.tsx
··· 177 177 post={post} 178 178 className={className} 179 179 pageId={pageId} 180 + clientHost={b.block.clientHost} 180 181 /> 181 182 ); 182 183 }
+4 -33
components/Blocks/BlueskyPostBlock/index.tsx
··· 1 1 import { useEntitySetContext } from "components/EntitySetProvider"; 2 - import { useEffect, useState } from "react"; 2 + import { useEffect } from "react"; 3 3 import { useEntity } from "src/replicache"; 4 4 import { useUIState } from "src/useUIState"; 5 5 import { BlockProps, BlockLayout } from "../Block"; 6 6 import { elementId } from "src/utils/elementId"; 7 7 import { focusBlock } from "src/utils/focusBlock"; 8 - import { AppBskyFeedDefs, AppBskyFeedPost, RichText } from "@atproto/api"; 8 + import { AppBskyFeedDefs } from "@atproto/api"; 9 9 import { PostNotAvailable } from "./BlueskyEmbed"; 10 10 import { BlueskyPostEmpty } from "./BlueskyEmpty"; 11 11 12 - import { Separator } from "components/Layout"; 13 - import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 14 - import { CommentTiny } from "components/Icons/CommentTiny"; 15 - import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 16 12 import { BskyPostContent } from "app/lish/[did]/[publication]/[rkey]/BskyPostContent"; 17 13 import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; 18 14 ··· 22 18 s.selectedBlocks.find((b) => b.value === props.entityID), 23 19 ); 24 20 let post = useEntity(props.entityID, "block/bluesky-post")?.data.value; 21 + let clientHost = useEntity(props.entityID, "bluesky-post/host")?.data.value; 25 22 26 23 useEffect(() => { 27 24 if (props.preview) return; ··· 64 61 ); 65 62 66 63 case AppBskyFeedDefs.isThreadViewPost(post): 67 - let record = post.post 68 - .record as AppBskyFeedDefs.FeedViewPost["post"]["record"]; 69 - let facets = record.facets; 70 - 71 - // silliness to get the text and timestamp from the record with proper types 72 - let text: string | null = null; 73 - let timestamp: string | undefined = undefined; 74 - if (AppBskyFeedPost.isRecord(record)) { 75 - text = (record as AppBskyFeedPost.Record).text; 76 - timestamp = (record as AppBskyFeedPost.Record).createdAt; 77 - } 78 - 79 - //getting the url to the post 80 - let postId = post.post.uri.split("/")[4]; 81 64 let postView = post.post as PostView; 82 - let url = `https://bsky.app/profile/${post.post.author.handle}/post/${postId}`; 83 65 84 66 return ( 85 67 <BlockLayout ··· 95 77 showEmbed={true} 96 78 avatarSize="large" 97 79 className="text-sm text-secondary " 80 + clientHost={clientHost} 98 81 /> 99 82 </BlockLayout> 100 83 ); 101 84 } 102 85 }; 103 - 104 - function PostDate(props: { timestamp: string }) { 105 - const formattedDate = useLocalizedDate(props.timestamp, { 106 - month: "short", 107 - day: "numeric", 108 - year: "numeric", 109 - hour: "numeric", 110 - minute: "numeric", 111 - hour12: true, 112 - }); 113 - return <div className="text-xs text-tertiary">{formattedDate}</div>; 114 - }
+3
lexicons/api/lexicons.ts
··· 1048 1048 type: 'ref', 1049 1049 ref: 'lex:com.atproto.repo.strongRef', 1050 1050 }, 1051 + clientHost: { 1052 + type: 'string', 1053 + }, 1051 1054 }, 1052 1055 }, 1053 1056 },
+1
lexicons/api/types/pub/leaflet/blocks/bskyPost.ts
··· 18 18 export interface Main { 19 19 $type?: 'pub.leaflet.blocks.bskyPost' 20 20 postRef: ComAtprotoRepoStrongRef.Main 21 + clientHost?: string 21 22 } 22 23 23 24 const hashMain = 'main'
+3
lexicons/pub/leaflet/blocks/bskyPost.json
··· 11 11 "postRef": { 12 12 "type": "ref", 13 13 "ref": "com.atproto.repo.strongRef" 14 + }, 15 + "clientHost": { 16 + "type": "string" 14 17 } 15 18 } 16 19 }
+1
lexicons/src/blocks.ts
··· 43 43 required: ["postRef"], 44 44 properties: { 45 45 postRef: { type: "ref", ref: "com.atproto.repo.strongRef" }, 46 + clientHost: { type: "string" }, 46 47 }, 47 48 }, 48 49 },
+4
src/replicache/attributes.ts
··· 151 151 type: "string", 152 152 cardinality: "one", 153 153 }, 154 + "bluesky-post/host": { 155 + type: "string", 156 + cardinality: "one", 157 + }, 154 158 } as const; 155 159 156 160 const ButtonBlockAttributes = {
+24 -13
src/utils/addLinkBlock.ts
··· 144 144 ) { 145 145 //construct bsky post uri from url 146 146 let urlParts = url?.split("/"); 147 + let host = urlParts ? urlParts[2] : "bsky.app"; // "bsky.app", "blacksky.community", "witchsky.app", etc. 147 148 let userDidOrHandle = urlParts ? urlParts[4] : ""; // "schlage.town" or "did:plc:jjsc5rflv3cpv6hgtqhn2dcm" 148 149 let collection = "app.bsky.feed.post"; 149 150 let postId = urlParts ? urlParts[6] : ""; ··· 152 153 let post = await getBlueskyPost(uri); 153 154 if (!post || post === undefined) return false; 154 155 155 - await rep.mutate.assertFact({ 156 - entity: entityID, 157 - attribute: "block/type", 158 - data: { type: "block-type-union", value: "bluesky-post" }, 159 - }); 160 - await rep?.mutate.assertFact({ 161 - entity: entityID, 162 - attribute: "block/bluesky-post", 163 - data: { 164 - type: "bluesky-post", 165 - //TODO: this is a hack to get rid of a nested Array buffer which cannot be frozen, which replicache does on write. 166 - value: JSON.parse(JSON.stringify(post.data.thread)), 156 + await rep.mutate.assertFact([ 157 + { 158 + entity: entityID, 159 + attribute: "block/type", 160 + data: { type: "block-type-union", value: "bluesky-post" }, 161 + }, 162 + { 163 + entity: entityID, 164 + attribute: "block/bluesky-post", 165 + data: { 166 + type: "bluesky-post", 167 + //TODO: this is a hack to get rid of a nested Array buffer which cannot be frozen, which replicache does on write. 168 + value: JSON.parse(JSON.stringify(post.data.thread)), 169 + }, 170 + }, 171 + { 172 + entity: entityID, 173 + attribute: "bluesky-post/host", 174 + data: { 175 + type: "string", 176 + value: host, 177 + }, 167 178 }, 168 - }); 179 + ]); 169 180 return true; 170 181 } 171 182 async function getBlueskyPost(uri: string) {