import React from 'react' import {observer} from 'mobx-react-lite' import {Linking, StyleSheet, View} from 'react-native' import Clipboard from '@react-native-clipboard/clipboard' import {AtUri} from '../../../third-party/uri' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {PostThreadViewPostModel} from 'state/models/post-thread-view' import {Link} from '../util/Link' import {RichText} from '../util/text/RichText' import {Text} from '../util/text/Text' import {PostDropdownBtn} from '../util/forms/DropdownButton' import * as Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' import {s} from 'lib/styles' import {ago} from 'lib/strings/time' import {pluralize} from 'lib/strings/helpers' import {useStores} from 'state/index' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/PostEmbeds' import {PostCtrls} from '../util/PostCtrls' import {PostMutedWrapper} from '../util/PostMuted' import {ErrorMessage} from '../util/error/ErrorMessage' import {usePalette} from 'lib/hooks/usePalette' const PARENT_REPLY_LINE_LENGTH = 8 export const PostThreadItem = observer(function PostThreadItem({ item, onPostReply, }: { item: PostThreadViewPostModel onPostReply: () => void }) { const pal = usePalette('default') const store = useStores() const [deleted, setDeleted] = React.useState(false) const record = item.postRecord const hasEngagement = item.post.upvoteCount || item.post.repostCount const itemUri = item.post.uri const itemCid = item.post.cid const itemHref = React.useMemo(() => { const urip = new AtUri(item.post.uri) return `/profile/${item.post.author.handle}/post/${urip.rkey}` }, [item.post.uri, item.post.author.handle]) const itemTitle = `Post by ${item.post.author.handle}` const authorHref = `/profile/${item.post.author.handle}` const authorTitle = item.post.author.handle const upvotesHref = React.useMemo(() => { const urip = new AtUri(item.post.uri) return `/profile/${item.post.author.handle}/post/${urip.rkey}/upvoted-by` }, [item.post.uri, item.post.author.handle]) const upvotesTitle = 'Likes on this post' const repostsHref = React.useMemo(() => { const urip = new AtUri(item.post.uri) return `/profile/${item.post.author.handle}/post/${urip.rkey}/reposted-by` }, [item.post.uri, item.post.author.handle]) const repostsTitle = 'Reposts of this post' const onPressReply = React.useCallback(() => { store.shell.openComposer({ replyTo: { uri: item.post.uri, cid: item.post.cid, text: record?.text as string, author: { handle: item.post.author.handle, displayName: item.post.author.displayName, avatar: item.post.author.avatar, }, }, onPost: onPostReply, }) }, [store, item, record, onPostReply]) const onPressToggleRepost = React.useCallback(() => { return item .toggleRepost() .catch(e => store.log.error('Failed to toggle repost', e)) }, [item, store]) const onPressToggleUpvote = React.useCallback(() => { return item .toggleUpvote() .catch(e => store.log.error('Failed to toggle upvote', e)) }, [item, store]) const onCopyPostText = React.useCallback(() => { Clipboard.setString(record?.text || '') Toast.show('Copied to clipboard') }, [record]) const onOpenTranslate = React.useCallback(() => { Linking.openURL( encodeURI(`https://translate.google.com/#auto|en|${record?.text || ''}`), ) }, [record]) const onDeletePost = React.useCallback(() => { item.delete().then( () => { setDeleted(true) Toast.show('Post deleted') }, e => { store.log.error('Failed to delete post', e) Toast.show('Failed to delete post, please try again') }, ) }, [item, store]) if (!record) { return } if (deleted) { return ( This post has been deleted. ) } if (item._isHighlightedPost) { return ( <> {item.post.author.displayName || item.post.author.handle} · {ago(item.post.indexedAt)} @{item.post.author.handle} {item.richText?.text ? ( ) : undefined} {item._isHighlightedPost && hasEngagement ? ( {item.post.repostCount ? ( {item.post.repostCount} {' '} {pluralize(item.post.repostCount, 'repost')} ) : ( <> )} {item.post.upvoteCount ? ( {item.post.upvoteCount} {' '} {pluralize(item.post.upvoteCount, 'like')} ) : ( <> )} ) : ( <> )} ) } else { return ( {item._showParentReplyLine && ( )} {item._showChildReplyLine && ( )} {item.richText?.text ? ( ) : undefined} {item._hasMore ? ( Continue thread... ) : undefined} ) } }) const styles = StyleSheet.create({ outer: { borderTopWidth: 1, paddingLeft: 10, }, outerHighlighted: { paddingTop: 2, paddingLeft: 6, paddingRight: 6, }, parentReplyLine: { position: 'absolute', left: 44, top: -1 * PARENT_REPLY_LINE_LENGTH + 6, height: PARENT_REPLY_LINE_LENGTH, borderLeftWidth: 2, }, childReplyLine: { position: 'absolute', left: 44, top: 65, bottom: 0, borderLeftWidth: 2, }, layout: { flexDirection: 'row', }, layoutAvi: { width: 70, paddingLeft: 10, paddingTop: 10, paddingBottom: 10, }, layoutContent: { flex: 1, paddingRight: 10, paddingTop: 10, paddingBottom: 10, }, meta: { flexDirection: 'row', paddingTop: 2, paddingBottom: 2, }, metaExpandedLine1: { paddingTop: 5, paddingBottom: 0, }, metaItem: { paddingRight: 5, maxWidth: 240, }, postTextContainer: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', paddingBottom: 8, paddingRight: 10, minHeight: 36, }, postTextLargeContainer: { paddingHorizontal: 0, paddingBottom: 10, }, expandedInfo: { flexDirection: 'row', padding: 10, borderTopWidth: 1, borderBottomWidth: 1, marginTop: 5, marginBottom: 15, }, expandedInfoItem: { marginRight: 10, }, loadMore: { flexDirection: 'row', justifyContent: 'space-between', borderTopWidth: 1, paddingLeft: 80, paddingRight: 20, paddingVertical: 10, marginBottom: 8, }, })