import React, {useState, useMemo} from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { AppBskyFeedDefs, AppBskyFeedPost, AtUri, PostModeration, RichText as RichTextAPI, } from '@atproto/api' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Link, TextLink} from '../util/Link' import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/post-embeds' import {PostCtrls} from '../util/post-ctrls/PostCtrls' import {ContentHider} from '../util/moderation/ContentHider' import {PostAlerts} from '../util/moderation/PostAlerts' import {Text} from '../util/text/Text' import {RichText} from '../util/text/RichText' import {PreviewableUserAvatar} from '../util/UserAvatar' import {s, colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {makeProfileLink} from 'lib/routes/links' import {MAX_POST_LINES} from 'lib/constants' import {countLines} from 'lib/strings/helpers' import {useModerationOpts} from '#/state/queries/preferences' import {useComposerControls} from '#/state/shell/composer' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' export function Post({ post, showReplyLine, style, }: { post: AppBskyFeedDefs.PostView showReplyLine?: boolean style?: StyleProp }) { const moderationOpts = useModerationOpts() const record = useMemo( () => AppBskyFeedPost.isRecord(post.record) && AppBskyFeedPost.validateRecord(post.record).success ? post.record : undefined, [post], ) const postShadowed = usePostShadow(post) const richText = useMemo( () => record ? new RichTextAPI({ text: record.text, facets: record.facets, }) : undefined, [record], ) const moderation = useMemo( () => (moderationOpts ? moderatePost(post, moderationOpts) : undefined), [moderationOpts, post], ) if (postShadowed === POST_TOMBSTONE) { return null } if (record && richText && moderation) { return ( ) } return null } function PostInner({ post, record, richText, moderation, showReplyLine, style, }: { post: Shadow record: AppBskyFeedPost.Record richText: RichTextAPI moderation: PostModeration showReplyLine?: boolean style?: StyleProp }) { const pal = usePalette('default') const {_} = useLingui() const {openComposer} = useComposerControls() const [limitLines, setLimitLines] = useState( () => countLines(richText?.text) >= MAX_POST_LINES, ) const itemUrip = new AtUri(post.uri) const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey) let replyAuthorDid = '' if (record.reply) { const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri) replyAuthorDid = urip.hostname } const onPressReply = React.useCallback(() => { openComposer({ replyTo: { uri: post.uri, cid: post.cid, text: record.text, author: { handle: post.author.handle, displayName: post.author.displayName, avatar: post.author.avatar, }, embed: post.embed, }, }) }, [openComposer, post, record]) const onPressShowMore = React.useCallback(() => { setLimitLines(false) }, [setLimitLines]) return ( {showReplyLine && } {replyAuthorDid !== '' && ( Reply to{' '} )} {richText.text ? ( ) : undefined} {limitLines ? ( ) : undefined} {post.embed ? ( ) : null} ) } const styles = StyleSheet.create({ outer: { paddingTop: 10, paddingRight: 15, paddingBottom: 5, paddingLeft: 10, borderTopWidth: 1, // @ts-ignore web only -prf cursor: 'pointer', }, layout: { flexDirection: 'row', }, layoutAvi: { width: 70, paddingLeft: 8, }, layoutContent: { flex: 1, }, alert: { marginBottom: 6, }, postTextContainer: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', }, replyLine: { position: 'absolute', left: 36, top: 70, bottom: 0, borderLeftWidth: 2, borderLeftColor: colors.gray2, }, contentHider: { marginBottom: 2, }, contentHiderChild: { marginTop: 6, }, })