Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
6.2 kB · 167 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168import { useEntitySetContext } from "components/EntitySetProvider";import { useEffect, useState } from "react";import { useEntity } from "src/replicache";import { useUIState } from "src/useUIState";import { BlockProps } from "../Block";import { elementId } from "src/utils/elementId";import { focusBlock } from "src/utils/focusBlock";import { AppBskyFeedDefs, AppBskyFeedPost, RichText } from "@atproto/api";import { BlueskyEmbed, PostNotAvailable } from "./BlueskyEmbed";import { BlueskyPostEmpty } from "./BlueskyEmpty";import { BlueskyRichText } from "./BlueskyRichText";import { Separator } from "components/Layout";import { BlueskyTiny } from "components/Icons/BlueskyTiny";import { CommentTiny } from "components/Icons/CommentTiny";import { useLocalizedDate } from "src/hooks/useLocalizedDate";
export const BlueskyPostBlock = (props: BlockProps & { preview?: boolean }) => { let { permissions } = useEntitySetContext(); let isSelected = useUIState((s) => s.selectedBlocks.find((b) => b.value === props.entityID), ); let post = useEntity(props.entityID, "block/bluesky-post")?.data.value;
useEffect(() => { if (props.preview) return; let input = document.getElementById(elementId.block(props.entityID).input); if (isSelected) { input?.focus(); } else input?.blur(); }, [isSelected, props.entityID, props.preview]);
switch (true) { case !post: if (!permissions.write) return null; return ( <label id={props.preview ? undefined : elementId.block(props.entityID).input} className={` w-full h-[104px] p-2 text-tertiary hover:text-accent-contrast hover:cursor-pointer flex flex-auto gap-2 items-center justify-center hover:border-2 border-dashed rounded-lg ${isSelected ? "border-2 border-tertiary" : "border border-border"} ${props.pageType === "canvas" && "bg-bg-page"}`} onMouseDown={() => { focusBlock( { type: props.type, value: props.entityID, parent: props.parent }, { type: "start" }, ); }} > <BlueskyPostEmpty {...props} /> </label> );
case AppBskyFeedDefs.isBlockedPost(post) || AppBskyFeedDefs.isBlockedAuthor(post) || AppBskyFeedDefs.isNotFoundPost(post): return ( <div className={`w-full ${isSelected ? "block-border-selected" : "block-border"}`} > <PostNotAvailable /> </div> );
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 url = `https://bsky.app/profile/${post.post.author.handle}/post/${postId}`;
return ( <div className={` flex flex-col gap-2 relative w-full overflow-hidden group/blueskyPostBlock sm:p-3 p-2 text-sm text-secondary bg-bg-page ${isSelected ? "block-border-selected " : "block-border"} `} > {post.post.author && record && ( <> <div className="bskyAuthor w-full flex items-center gap-2"> {post.post.author?.avatar ? ( <img src={post.post.author?.avatar} alt={`${post.post.author?.displayName}'s avatar`} className="shrink-0 w-8 h-8 rounded-full border border-border-light" /> ) : ( <div className="shrink-0 w-8 h-8 rounded-full border border-border-light bg-border"></div> )} <div className="grow flex flex-col gap-0.5 leading-tight"> <div className=" font-bold text-secondary"> {post.post.author?.displayName} </div> <a className="text-xs text-tertiary hover:underline" target="_blank" href={`https://bsky.app/profile/${post.post.author?.handle}`} > @{post.post.author?.handle} </a> </div> </div>
<div className="flex flex-col gap-2 "> <div> <pre className="whitespace-pre-wrap"> {BlueskyRichText({ record: record as AppBskyFeedPost.Record | null, })} </pre> </div> {post.post.embed && ( <BlueskyEmbed embed={post.post.embed} postUrl={url} /> )} </div> </> )} <div className="w-full flex gap-2 items-center justify-between"> {timestamp && <PostDate timestamp={timestamp} />} <div className="flex gap-2 items-center"> {post.post.replyCount && post.post.replyCount > 0 && ( <> <a className="flex items-center gap-1 hover:no-underline" target="_blank" href={url} > {post.post.replyCount} <CommentTiny /> </a> <Separator classname="h-4" /> </> )}
<a className="" target="_blank" href={url}> <BlueskyTiny /> </a> </div> </div> </div> ); }};
function PostDate(props: { timestamp: string }) { const formattedDate = useLocalizedDate(props.timestamp, { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "numeric", hour12: true, }); return <div className="text-xs text-tertiary">{formattedDate}</div>;}