a tool for shared writing and social publishing
at update/reader 114 lines 4.1 kB view raw
1import { useEntitySetContext } from "components/EntitySetProvider"; 2import { useEffect, useState } from "react"; 3import { useEntity } from "src/replicache"; 4import { useUIState } from "src/useUIState"; 5import { BlockProps, BlockLayout } from "../Block"; 6import { elementId } from "src/utils/elementId"; 7import { focusBlock } from "src/utils/focusBlock"; 8import { AppBskyFeedDefs, AppBskyFeedPost, RichText } from "@atproto/api"; 9import { PostNotAvailable } from "./BlueskyEmbed"; 10import { BlueskyPostEmpty } from "./BlueskyEmpty"; 11 12import { Separator } from "components/Layout"; 13import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 14import { CommentTiny } from "components/Icons/CommentTiny"; 15import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 16import { BskyPostContent } from "app/lish/[did]/[publication]/[rkey]/BskyPostContent"; 17import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; 18 19export const BlueskyPostBlock = (props: BlockProps & { preview?: boolean }) => { 20 let { permissions } = useEntitySetContext(); 21 let isSelected = useUIState((s) => 22 s.selectedBlocks.find((b) => b.value === props.entityID), 23 ); 24 let post = useEntity(props.entityID, "block/bluesky-post")?.data.value; 25 26 useEffect(() => { 27 if (props.preview) return; 28 let input = document.getElementById(elementId.block(props.entityID).input); 29 if (isSelected) { 30 input?.focus(); 31 } else input?.blur(); 32 }, [isSelected, props.entityID, props.preview]); 33 34 switch (true) { 35 case !post: 36 if (!permissions.write) return null; 37 return ( 38 <label 39 id={props.preview ? undefined : elementId.block(props.entityID).input} 40 className={` 41 w-full h-[104px] p-2 42 text-tertiary hover:text-accent-contrast hover:cursor-pointer 43 flex flex-auto gap-2 items-center justify-center hover:border-2 border-dashed rounded-lg 44 ${isSelected ? "border-2 border-tertiary" : "border border-border"} 45 ${props.pageType === "canvas" && "bg-bg-page"}`} 46 onMouseDown={() => { 47 focusBlock( 48 { type: props.type, value: props.entityID, parent: props.parent }, 49 { type: "start" }, 50 ); 51 }} 52 > 53 <BlueskyPostEmpty {...props} /> 54 </label> 55 ); 56 57 case AppBskyFeedDefs.isBlockedPost(post) || 58 AppBskyFeedDefs.isBlockedAuthor(post) || 59 AppBskyFeedDefs.isNotFoundPost(post): 60 return ( 61 <BlockLayout isSelected={!!isSelected} className="w-full"> 62 <PostNotAvailable /> 63 </BlockLayout> 64 ); 65 66 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 let postView = post.post as PostView; 82 let url = `https://bsky.app/profile/${post.post.author.handle}/post/${postId}`; 83 84 return ( 85 <BlockLayout 86 isSelected={!!isSelected} 87 hasBackground="page" 88 borderOnHover 89 className="blueskyPostBlock sm:px-3! sm:py-2! px-2! py-1!" 90 > 91 <BskyPostContent 92 post={postView} 93 parent={undefined} 94 showBlueskyLink={true} 95 showEmbed={true} 96 avatarSize="large" 97 className="text-sm text-secondary " 98 /> 99 </BlockLayout> 100 ); 101 } 102}; 103 104function 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}