diff options
Diffstat (limited to 'src/view/com/post/Post.tsx')
-rw-r--r-- | src/view/com/post/Post.tsx | 271 |
1 files changed, 89 insertions, 182 deletions
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 4ec9db77f..2e8019e71 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -1,19 +1,14 @@ -import React, {useState} from 'react' +import React, {useState, useMemo} from 'react' +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { - ActivityIndicator, - Linking, - StyleProp, - StyleSheet, - View, - ViewStyle, -} from 'react-native' -import {AppBskyFeedPost as FeedPost} from '@atproto/api' -import {observer} from 'mobx-react-lite' -import Clipboard from '@react-native-clipboard/clipboard' -import {AtUri} from '@atproto/api' + AppBskyFeedDefs, + AppBskyFeedPost, + AtUri, + moderatePost, + PostModeration, + RichText as RichTextAPI, +} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {PostThreadModel} from 'state/models/content/post-thread' -import {PostThreadItemModel} from 'state/models/content/post-thread-item' import {Link, TextLink} from '../util/Link' import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' @@ -23,169 +18,109 @@ 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 * as Toast from '../util/Toast' import {PreviewableUserAvatar} from '../util/UserAvatar' -import {useStores} from 'state/index' import {s, colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' -import {getTranslatorLink} from '../../../locale/helpers' import {makeProfileLink} from 'lib/routes/links' import {MAX_POST_LINES} from 'lib/constants' import {countLines} from 'lib/strings/helpers' -import {logger} from '#/logger' +import {useModerationOpts} from '#/state/queries/preferences' +import {useComposerControls} from '#/state/shell/composer' +import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' -export const Post = observer(function PostImpl({ - view, +export function Post({ + post, showReplyLine, - hideError, style, }: { - view: PostThreadModel + post: AppBskyFeedDefs.PostView showReplyLine?: boolean - hideError?: boolean style?: StyleProp<ViewStyle> }) { - const pal = usePalette('default') - const [deleted, setDeleted] = useState(false) - - // deleted - // = - if (deleted) { - return <View /> - } - - // loading - // = - if (!view.hasContent && view.isLoading) { - return ( - <View style={pal.view}> - <ActivityIndicator /> - </View> - ) + const moderationOpts = useModerationOpts() + const record = useMemo<AppBskyFeedPost.Record | undefined>( + () => + 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 } - - // error - // = - if (view.hasError || !view.thread || !view.thread?.postRecord) { - if (hideError) { - return <View /> - } + if (record && richText && moderation) { return ( - <View style={pal.view}> - <Text>{view.error || 'Thread not found'}</Text> - </View> + <PostInner + post={postShadowed} + record={record} + richText={richText} + moderation={moderation} + showReplyLine={showReplyLine} + style={style} + /> ) } + return null +} - // loaded - // = - - return ( - <PostLoaded - item={view.thread} - record={view.thread.postRecord} - setDeleted={setDeleted} - showReplyLine={showReplyLine} - style={style} - /> - ) -}) - -const PostLoaded = observer(function PostLoadedImpl({ - item, +function PostInner({ + post, record, - setDeleted, + richText, + moderation, showReplyLine, style, }: { - item: PostThreadItemModel - record: FeedPost.Record - setDeleted: (v: boolean) => void + post: Shadow<AppBskyFeedDefs.PostView> + record: AppBskyFeedPost.Record + richText: RichTextAPI + moderation: PostModeration showReplyLine?: boolean style?: StyleProp<ViewStyle> }) { const pal = usePalette('default') - const store = useStores() - const [limitLines, setLimitLines] = React.useState( - countLines(item.richText?.text) >= MAX_POST_LINES, + const {openComposer} = useComposerControls() + const [limitLines, setLimitLines] = useState( + () => countLines(richText?.text) >= MAX_POST_LINES, ) - const itemUri = item.post.uri - const itemCid = item.post.cid - const itemUrip = new AtUri(item.post.uri) - const itemHref = makeProfileLink(item.post.author, 'post', itemUrip.rkey) - const itemTitle = `Post by ${item.post.author.handle}` + 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 translatorUrl = getTranslatorLink( - record?.text || '', - store.preferences.primaryLanguage, - ) - const onPressReply = React.useCallback(() => { - store.shell.openComposer({ + openComposer({ replyTo: { - uri: item.post.uri, - cid: item.post.cid, - text: record.text as string, + uri: post.uri, + cid: post.cid, + text: record.text, author: { - handle: item.post.author.handle, - displayName: item.post.author.displayName, - avatar: item.post.author.avatar, + handle: post.author.handle, + displayName: post.author.displayName, + avatar: post.author.avatar, }, }, }) - }, [store, item, record]) - - const onPressToggleRepost = React.useCallback(() => { - return item - .toggleRepost() - .catch(e => logger.error('Failed to toggle repost', {error: e})) - }, [item]) - - const onPressToggleLike = React.useCallback(() => { - return item - .toggleLike() - .catch(e => logger.error('Failed to toggle like', {error: e})) - }, [item]) - - const onCopyPostText = React.useCallback(() => { - Clipboard.setString(record.text) - Toast.show('Copied to clipboard') - }, [record]) - - const onOpenTranslate = React.useCallback(() => { - Linking.openURL(translatorUrl) - }, [translatorUrl]) - - const onToggleThreadMute = React.useCallback(async () => { - try { - await item.toggleThreadMute() - if (item.isThreadMuted) { - Toast.show('You will no longer receive notifications for this thread') - } else { - Toast.show('You will now receive notifications for this thread') - } - } catch (e) { - logger.error('Failed to toggle thread mute', {error: e}) - } - }, [item]) - - const onDeletePost = React.useCallback(() => { - item.delete().then( - () => { - setDeleted(true) - Toast.show('Post deleted') - }, - e => { - logger.error('Failed to delete post', {error: e}) - Toast.show('Failed to delete post, please try again') - }, - ) - }, [item, setDeleted]) + }, [openComposer, post, record]) const onPressShowMore = React.useCallback(() => { setLimitLines(false) @@ -198,17 +133,17 @@ const PostLoaded = observer(function PostLoadedImpl({ <View style={styles.layoutAvi}> <PreviewableUserAvatar size={52} - did={item.post.author.did} - handle={item.post.author.handle} - avatar={item.post.author.avatar} - moderation={item.moderation.avatar} + did={post.author.did} + handle={post.author.handle} + avatar={post.author.avatar} + moderation={moderation.avatar} /> </View> <View style={styles.layoutContent}> <PostMeta - author={item.post.author} - authorHasWarning={!!item.post.author.labels?.length} - timestamp={item.post.indexedAt} + author={post.author} + authorHasWarning={!!post.author.labels?.length} + timestamp={post.indexedAt} postHref={itemHref} /> {replyAuthorDid !== '' && ( @@ -234,19 +169,16 @@ const PostLoaded = observer(function PostLoadedImpl({ </View> )} <ContentHider - moderation={item.moderation.content} + moderation={moderation.content} style={styles.contentHider} childContainerStyle={styles.contentHiderChild}> - <PostAlerts - moderation={item.moderation.content} - style={styles.alert} - /> - {item.richText?.text ? ( + <PostAlerts moderation={moderation.content} style={styles.alert} /> + {richText.text ? ( <View style={styles.postTextContainer}> <RichText testID="postText" type="post-text" - richText={item.richText} + richText={richText} lineHeight={1.3} numberOfLines={limitLines ? MAX_POST_LINES : undefined} style={s.flex1} @@ -261,45 +193,20 @@ const PostLoaded = observer(function PostLoadedImpl({ href="#" /> ) : undefined} - {item.post.embed ? ( + {post.embed ? ( <ContentHider - moderation={item.moderation.embed} + moderation={moderation.embed} style={styles.contentHider}> - <PostEmbeds - embed={item.post.embed} - moderation={item.moderation.embed} - /> + <PostEmbeds embed={post.embed} moderation={moderation.embed} /> </ContentHider> ) : null} </ContentHider> - <PostCtrls - itemUri={itemUri} - itemCid={itemCid} - itemHref={itemHref} - itemTitle={itemTitle} - author={item.post.author} - indexedAt={item.post.indexedAt} - text={item.richText?.text || record.text} - isAuthor={item.post.author.did === store.me.did} - replyCount={item.post.replyCount} - repostCount={item.post.repostCount} - likeCount={item.post.likeCount} - isReposted={!!item.post.viewer?.repost} - isLiked={!!item.post.viewer?.like} - isThreadMuted={item.isThreadMuted} - onPressReply={onPressReply} - onPressToggleRepost={onPressToggleRepost} - onPressToggleLike={onPressToggleLike} - onCopyPostText={onCopyPostText} - onOpenTranslate={onOpenTranslate} - onToggleThreadMute={onToggleThreadMute} - onDeletePost={onDeletePost} - /> + <PostCtrls post={post} record={record} onPressReply={onPressReply} /> </View> </View> </Link> ) -}) +} const styles = StyleSheet.create({ outer: { |