remove precacheThreadPostProfiles (#3729)
* remove `precacheThreadPostProfiles` * add `displayName` to `PreviewableUserAvatar` * memo * use `precacheProfile` * pass `profile` directly to `PreviewableUserAvatar` * update the `UserAvatar`'s props * remove feed cache * one more spot * rm unused queryClient * Don't call fn unnecessarily * Preload for display name too * try notification item * add to feeditem * and finally, precache for post threads * timestamp * Fix * onBeforePress --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>zio/stable
parent
ce85375c85
commit
7eb1444f2c
|
@ -1,25 +1,27 @@
|
|||
import React, {ComponentProps} from 'react'
|
||||
import {StyleSheet, Pressable, View, ViewStyle, StyleProp} from 'react-native'
|
||||
import {ModerationUI} from '@atproto/api'
|
||||
import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
||||
import {AppBskyActorDefs, ModerationUI} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
|
||||
import {addStyle} from 'lib/styles'
|
||||
|
||||
import {useTheme, atoms as a} from '#/alf'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
// import {Link} from '#/components/Link' TODO this imposes some styles that screw things up
|
||||
import {Link} from '#/view/com/util/Link'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {
|
||||
ModerationDetailsDialog,
|
||||
useModerationDetailsDialogControl,
|
||||
} from '#/components/moderation/ModerationDetailsDialog'
|
||||
import {Text} from '#/components/Typography'
|
||||
// import {Link} from '#/components/Link' TODO this imposes some styles that screw things up
|
||||
import {Link} from '#/view/com/util/Link'
|
||||
|
||||
interface Props extends ComponentProps<typeof Link> {
|
||||
iconSize: number
|
||||
iconStyles: StyleProp<ViewStyle>
|
||||
modui: ModerationUI
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
}
|
||||
|
||||
export function PostHider({
|
||||
|
@ -30,8 +32,10 @@ export function PostHider({
|
|||
children,
|
||||
iconSize,
|
||||
iconStyles,
|
||||
profile,
|
||||
...props
|
||||
}: Props) {
|
||||
const queryClient = useQueryClient()
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const [override, setOverride] = React.useState(false)
|
||||
|
@ -39,6 +43,10 @@ export function PostHider({
|
|||
const blur = modui.blurs[0]
|
||||
const desc = useModerationCauseDescription(blur)
|
||||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
precacheProfile(queryClient, profile)
|
||||
}, [queryClient, profile])
|
||||
|
||||
if (!blur) {
|
||||
return (
|
||||
<Link
|
||||
|
@ -46,6 +54,7 @@ export function PostHider({
|
|||
style={style}
|
||||
href={href}
|
||||
accessible={false}
|
||||
onBeforePress={onBeforePress}
|
||||
{...props}>
|
||||
{children}
|
||||
</Link>
|
||||
|
|
|
@ -113,12 +113,7 @@ export function MessagesListScreen({}: Props) {
|
|||
<Link
|
||||
to={`/messages/${item.profile.handle}`}
|
||||
style={[a.flex_1, a.pl_md, a.py_sm, a.gap_md, a.pr_2xl]}>
|
||||
<PreviewableUserAvatar
|
||||
did={item.profile.did}
|
||||
handle={item.profile.handle}
|
||||
size={44}
|
||||
avatar={item.profile.avatar}
|
||||
/>
|
||||
<PreviewableUserAvatar profile={item.profile} size={44} />
|
||||
<View style={[a.flex_1]}>
|
||||
<View
|
||||
style={[
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
QueryClient,
|
||||
QueryKey,
|
||||
useInfiniteQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||
|
@ -33,7 +32,6 @@ import {BSKY_FEED_OWNER_DIDS} from 'lib/constants'
|
|||
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
|
||||
import {useFeedTuners} from '../preferences/feed-tuners'
|
||||
import {useModerationOpts} from './preferences'
|
||||
import {precacheFeedPostProfiles} from './profile'
|
||||
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
|
||||
|
||||
type ActorDid = string
|
||||
|
@ -102,7 +100,6 @@ export function usePostFeedQuery(
|
|||
params?: FeedParams,
|
||||
opts?: {enabled?: boolean; ignoreFilterFor?: string},
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const feedTuners = useFeedTuners(feedDesc)
|
||||
const moderationOpts = useModerationOpts()
|
||||
const {getAgent} = useAgent()
|
||||
|
@ -151,7 +148,6 @@ export function usePostFeedQuery(
|
|||
|
||||
try {
|
||||
const res = await api.fetch({cursor, limit: PAGE_SIZE})
|
||||
precacheFeedPostProfiles(queryClient, res.feed)
|
||||
|
||||
/*
|
||||
* If this is a public view, we need to check if posts fail moderation.
|
||||
|
|
|
@ -11,7 +11,6 @@ import {useAgent} from '#/state/session'
|
|||
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from 'state/queries/search-posts'
|
||||
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from './notifications/feed'
|
||||
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from './post-feed'
|
||||
import {precacheThreadPostProfiles} from './profile'
|
||||
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
|
||||
|
||||
const RQKEY_ROOT = 'post-thread'
|
||||
|
@ -73,9 +72,7 @@ export function usePostThreadQuery(uri: string | undefined) {
|
|||
async queryFn() {
|
||||
const res = await getAgent().getPostThread({uri: uri!})
|
||||
if (res.success) {
|
||||
const nodes = responseToThreadNodes(res.data.thread)
|
||||
precacheThreadPostProfiles(queryClient, nodes)
|
||||
return nodes
|
||||
return responseToThreadNodes(res.data.thread)
|
||||
}
|
||||
return {type: 'unknown', uri: uri!}
|
||||
},
|
||||
|
|
|
@ -4,9 +4,6 @@ import {
|
|||
AppBskyActorDefs,
|
||||
AppBskyActorGetProfile,
|
||||
AppBskyActorProfile,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
AtUri,
|
||||
BskyAgent,
|
||||
} from '@atproto/api'
|
||||
|
@ -29,7 +26,6 @@ import {updateProfileShadow} from '../cache/profile-shadow'
|
|||
import {useAgent, useSession} from '../session'
|
||||
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
|
||||
import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
|
||||
import {ThreadNode} from './post-thread'
|
||||
|
||||
const RQKEY_ROOT = 'profile'
|
||||
export const RQKEY = (did: string) => [RQKEY_ROOT, did]
|
||||
|
@ -477,56 +473,6 @@ export function precacheProfile(
|
|||
queryClient.setQueryData(profileBasicQueryKey(profile.did), profile)
|
||||
}
|
||||
|
||||
export function precacheFeedPostProfiles(
|
||||
queryClient: QueryClient,
|
||||
posts: AppBskyFeedDefs.FeedViewPost[],
|
||||
) {
|
||||
for (const post of posts) {
|
||||
// Save the author of the post every time
|
||||
precacheProfile(queryClient, post.post.author)
|
||||
precachePostEmbedProfile(queryClient, post.post.embed)
|
||||
|
||||
// Cache parent author and embeds
|
||||
const parent = post.reply?.parent
|
||||
if (AppBskyFeedDefs.isPostView(parent)) {
|
||||
precacheProfile(queryClient, parent.author)
|
||||
precachePostEmbedProfile(queryClient, parent.embed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function precachePostEmbedProfile(
|
||||
queryClient: QueryClient,
|
||||
embed: AppBskyFeedDefs.PostView['embed'],
|
||||
) {
|
||||
if (AppBskyEmbedRecord.isView(embed)) {
|
||||
if (AppBskyEmbedRecord.isViewRecord(embed.record)) {
|
||||
precacheProfile(queryClient, embed.record.author)
|
||||
}
|
||||
} else if (AppBskyEmbedRecordWithMedia.isView(embed)) {
|
||||
if (AppBskyEmbedRecord.isViewRecord(embed.record.record)) {
|
||||
precacheProfile(queryClient, embed.record.record.author)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function precacheThreadPostProfiles(
|
||||
queryClient: QueryClient,
|
||||
node: ThreadNode,
|
||||
) {
|
||||
if (node.type === 'post') {
|
||||
precacheProfile(queryClient, node.post.author)
|
||||
if (node.parent) {
|
||||
precacheThreadPostProfiles(queryClient, node.parent)
|
||||
}
|
||||
if (node.replies?.length) {
|
||||
for (const reply of node.replies) {
|
||||
precacheThreadPostProfiles(queryClient, reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function whenAppViewReady(
|
||||
getAgent: () => BskyAgent,
|
||||
actor: string,
|
||||
|
|
|
@ -86,9 +86,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
|||
)}>
|
||||
<PreviewableUserAvatar
|
||||
size={50}
|
||||
did={replyTo.author.did}
|
||||
handle={replyTo.author.handle}
|
||||
avatar={replyTo.author.avatar}
|
||||
profile={replyTo.author}
|
||||
moderation={replyTo.moderation?.ui('avatar')}
|
||||
type={replyTo.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {FeedNotification} from '#/state/queries/notifications/feed'
|
||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||
|
@ -36,6 +37,7 @@ import {pluralize} from 'lib/strings/helpers'
|
|||
import {niceDate} from 'lib/strings/time'
|
||||
import {colors, s} from 'lib/styles'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {Link as NewLink} from '#/components/Link'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
import {FeedSourceCard} from '../feeds/FeedSourceCard'
|
||||
|
@ -52,13 +54,9 @@ const MAX_AUTHORS = 5
|
|||
const EXPANDED_AUTHOR_EL_HEIGHT = 35
|
||||
|
||||
interface Author {
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
href: string
|
||||
did: string
|
||||
handle: string
|
||||
displayName?: string
|
||||
avatar?: string
|
||||
moderation: ModerationDecision
|
||||
associated?: AppBskyActorDefs.ProfileAssociated
|
||||
}
|
||||
|
||||
let FeedItem = ({
|
||||
|
@ -68,6 +66,7 @@ let FeedItem = ({
|
|||
item: FeedNotification
|
||||
moderationOpts: ModerationOpts
|
||||
}): React.ReactNode => {
|
||||
const queryClient = useQueryClient()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false)
|
||||
|
@ -95,28 +94,22 @@ let FeedItem = ({
|
|||
setAuthorsExpanded(currentlyExpanded => !currentlyExpanded)
|
||||
}
|
||||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
precacheProfile(queryClient, item.notification.author)
|
||||
}, [queryClient, item.notification.author])
|
||||
|
||||
const authors: Author[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
profile: item.notification.author,
|
||||
href: makeProfileLink(item.notification.author),
|
||||
did: item.notification.author.did,
|
||||
handle: item.notification.author.handle,
|
||||
displayName: item.notification.author.displayName,
|
||||
avatar: item.notification.author.avatar,
|
||||
moderation: moderateProfile(item.notification.author, moderationOpts),
|
||||
associated: item.notification.author.associated,
|
||||
},
|
||||
...(item.additional?.map(({author}) => {
|
||||
return {
|
||||
href: makeProfileLink(author),
|
||||
did: author.did,
|
||||
handle: author.handle,
|
||||
displayName: author.displayName,
|
||||
avatar: author.avatar,
|
||||
moderation: moderateProfile(author, moderationOpts),
|
||||
associated: author.associated,
|
||||
}
|
||||
}) || []),
|
||||
...(item.additional?.map(({author}) => ({
|
||||
profile: author,
|
||||
href: makeProfileLink(author),
|
||||
moderation: moderateProfile(author, moderationOpts),
|
||||
})) || []),
|
||||
]
|
||||
}, [item, moderationOpts])
|
||||
|
||||
|
@ -201,7 +194,8 @@ let FeedItem = ({
|
|||
accessible={
|
||||
(item.type === 'post-like' && authors.length === 1) ||
|
||||
item.type === 'repost'
|
||||
}>
|
||||
}
|
||||
onBeforePress={onBeforePress}>
|
||||
<View style={styles.layoutIcon}>
|
||||
{/* TODO: Prevent conditional rendering and move toward composable
|
||||
notifications for clearer accessibility labeling */}
|
||||
|
@ -231,7 +225,7 @@ let FeedItem = ({
|
|||
style={[pal.text, s.bold]}
|
||||
href={authors[0].href}
|
||||
text={sanitizeDisplayName(
|
||||
authors[0].displayName || authors[0].handle,
|
||||
authors[0].profile.displayName || authors[0].profile.handle,
|
||||
)}
|
||||
disableMismatchWarning
|
||||
/>
|
||||
|
@ -339,11 +333,9 @@ function CondensedAuthorsList({
|
|||
<View style={styles.avis}>
|
||||
<PreviewableUserAvatar
|
||||
size={35}
|
||||
did={authors[0].did}
|
||||
handle={authors[0].handle}
|
||||
avatar={authors[0].avatar}
|
||||
profile={authors[0].profile}
|
||||
moderation={authors[0].moderation.ui('avatar')}
|
||||
type={authors[0].associated?.labeler ? 'labeler' : 'user'}
|
||||
type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -360,11 +352,9 @@ function CondensedAuthorsList({
|
|||
<View key={author.href} style={s.mr5}>
|
||||
<PreviewableUserAvatar
|
||||
size={35}
|
||||
did={author.did}
|
||||
handle={author.handle}
|
||||
avatar={author.avatar}
|
||||
profile={author.profile}
|
||||
moderation={author.moderation.ui('avatar')}
|
||||
type={author.associated?.labeler ? 'labeler' : 'user'}
|
||||
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
|
@ -415,20 +405,20 @@ function ExpandedAuthorsList({
|
|||
]}>
|
||||
{authors.map(author => (
|
||||
<NewLink
|
||||
key={author.did}
|
||||
key={author.profile.did}
|
||||
label={_(msg`See profile`)}
|
||||
to={makeProfileLink({
|
||||
did: author.did,
|
||||
handle: author.handle,
|
||||
did: author.profile.did,
|
||||
handle: author.profile.handle,
|
||||
})}
|
||||
style={styles.expandedAuthor}>
|
||||
<View style={styles.expandedAuthorAvi}>
|
||||
<ProfileHoverCard did={author.did}>
|
||||
<ProfileHoverCard did={author.profile.did}>
|
||||
<UserAvatar
|
||||
size={35}
|
||||
avatar={author.avatar}
|
||||
avatar={author.profile.avatar}
|
||||
moderation={author.moderation.ui('avatar')}
|
||||
type={author.associated?.labeler ? 'labeler' : 'user'}
|
||||
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
</ProfileHoverCard>
|
||||
</View>
|
||||
|
@ -438,10 +428,12 @@ function ExpandedAuthorsList({
|
|||
numberOfLines={1}
|
||||
style={pal.text}
|
||||
lineHeight={1.2}>
|
||||
{sanitizeDisplayName(author.displayName || author.handle)}
|
||||
{sanitizeDisplayName(
|
||||
author.profile.displayName || author.profile.handle,
|
||||
)}
|
||||
|
||||
<Text style={[pal.textLight]} lineHeight={1.2}>
|
||||
{sanitizeHandle(author.handle)}
|
||||
{sanitizeHandle(author.profile.handle)}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
@ -249,9 +249,7 @@ let PostThreadItemLoaded = ({
|
|||
<View style={[styles.layoutAvi, {paddingBottom: 8}]}>
|
||||
<PreviewableUserAvatar
|
||||
size={42}
|
||||
did={post.author.did}
|
||||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
profile={post.author}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
@ -399,7 +397,8 @@ let PostThreadItemLoaded = ({
|
|||
isThreadedChild
|
||||
? {marginRight: 4}
|
||||
: {marginLeft: 2, marginRight: 2}
|
||||
}>
|
||||
}
|
||||
profile={post.author}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
|
@ -440,9 +439,7 @@ let PostThreadItemLoaded = ({
|
|||
<View style={styles.layoutAvi}>
|
||||
<PreviewableUserAvatar
|
||||
size={38}
|
||||
did={post.author.did}
|
||||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
profile={post.author}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
|
|
@ -21,7 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {countLines} from 'lib/strings/helpers'
|
||||
import {colors, s} from 'lib/styles'
|
||||
import {RQKEY as RQKEY_URI} from 'state/queries/resolve-uri'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
import {RichText} from '#/components/RichText'
|
||||
|
@ -135,8 +135,8 @@ function PostInner({
|
|||
}, [setLimitLines])
|
||||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
queryClient.setQueryData(RQKEY_URI(post.author.handle), post.author.did)
|
||||
}, [queryClient, post.author.handle, post.author.did])
|
||||
precacheProfile(queryClient, post.author)
|
||||
}, [queryClient, post.author])
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
@ -148,9 +148,7 @@ function PostInner({
|
|||
<View style={styles.layoutAvi}>
|
||||
<PreviewableUserAvatar
|
||||
size={52}
|
||||
did={post.author.did}
|
||||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
profile={post.author}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
|
@ -24,6 +25,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
|
|||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {countLines} from 'lib/strings/helpers'
|
||||
import {s} from 'lib/styles'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
|
@ -106,6 +108,7 @@ let FeedItemInner = ({
|
|||
isThreadLastChild?: boolean
|
||||
isThreadParent?: boolean
|
||||
}): React.ReactNode => {
|
||||
const queryClient = useQueryClient()
|
||||
const {openComposer} = useComposerControls()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
@ -135,6 +138,10 @@ let FeedItemInner = ({
|
|||
})
|
||||
}, [post, record, openComposer, moderation])
|
||||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
precacheProfile(queryClient, post.author)
|
||||
}, [queryClient, post.author])
|
||||
|
||||
const outerStyles = [
|
||||
styles.outer,
|
||||
{
|
||||
|
@ -153,7 +160,8 @@ let FeedItemInner = ({
|
|||
style={outerStyles}
|
||||
href={href}
|
||||
noFeedback
|
||||
accessible={false}>
|
||||
accessible={false}
|
||||
onBeforePress={onBeforePress}>
|
||||
<View style={{flexDirection: 'row', gap: 10, paddingLeft: 8}}>
|
||||
<View style={{width: 52}}>
|
||||
{isThreadChild && (
|
||||
|
@ -240,9 +248,7 @@ let FeedItemInner = ({
|
|||
<View style={styles.layoutAvi}>
|
||||
<PreviewableUserAvatar
|
||||
size={52}
|
||||
did={post.author.did}
|
||||
handle={post.author.handle}
|
||||
avatar={post.author.avatar}
|
||||
profile={post.author}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
|
|
@ -20,8 +20,7 @@ import {makeProfileLink} from 'lib/routes/links'
|
|||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {s} from 'lib/styles'
|
||||
import {profileBasicQueryKey as RQKEY_PROFILE_BASIC} from 'state/queries/profile'
|
||||
import {RQKEY as RQKEY_URI} from 'state/queries/resolve-uri'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||
|
@ -58,9 +57,7 @@ export function ProfileCard({
|
|||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
onPress?.()
|
||||
|
||||
queryClient.setQueryData(RQKEY_URI(profile.handle), profile.did)
|
||||
queryClient.setQueryData(RQKEY_PROFILE_BASIC(profile.did), profile)
|
||||
precacheProfile(queryClient, profile)
|
||||
}, [onPress, profile, queryClient])
|
||||
|
||||
if (!moderationOpts) {
|
||||
|
@ -91,9 +88,7 @@ export function ProfileCard({
|
|||
<View style={styles.layoutAvi}>
|
||||
<PreviewableUserAvatar
|
||||
size={40}
|
||||
did={profile.did}
|
||||
handle={profile.handle}
|
||||
avatar={profile.avatar}
|
||||
profile={profile}
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={isLabeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
@ -238,9 +233,7 @@ function FollowersList({
|
|||
<View style={[styles.followedByAvi, pal.view]}>
|
||||
<PreviewableUserAvatar
|
||||
size={32}
|
||||
did={f.did}
|
||||
handle={f.handle}
|
||||
avatar={f.avatar}
|
||||
profile={f}
|
||||
moderation={mod.ui('avatar')}
|
||||
type={f.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
|
|
@ -220,8 +220,7 @@ function SuggestedFollow({
|
|||
]}>
|
||||
<PreviewableUserAvatar
|
||||
size={60}
|
||||
did={profile.did}
|
||||
handle={profile.handle}
|
||||
profile={profile}
|
||||
avatar={profile.avatar}
|
||||
moderation={moderation.ui('avatar')}
|
||||
/>
|
||||
|
|
|
@ -148,6 +148,7 @@ export const TextLink = memo(function TextLink({
|
|||
dataSet,
|
||||
title,
|
||||
onPress,
|
||||
onBeforePress,
|
||||
disableMismatchWarning,
|
||||
navigationAction,
|
||||
anchorNoUnderline,
|
||||
|
@ -165,6 +166,7 @@ export const TextLink = memo(function TextLink({
|
|||
disableMismatchWarning?: boolean
|
||||
navigationAction?: 'push' | 'replace' | 'navigate'
|
||||
anchorNoUnderline?: boolean
|
||||
onBeforePress?: () => void
|
||||
} & TextProps) {
|
||||
const {...props} = useLinkProps({to: sanitizeUrl(href)})
|
||||
const navigation = useNavigationDeduped()
|
||||
|
@ -202,6 +204,7 @@ export const TextLink = memo(function TextLink({
|
|||
// Let the browser handle opening in new tab etc.
|
||||
return
|
||||
}
|
||||
onBeforePress?.()
|
||||
if (onPress) {
|
||||
e?.preventDefault?.()
|
||||
// @ts-ignore function signature differs by platform -prf
|
||||
|
@ -226,6 +229,7 @@ export const TextLink = memo(function TextLink({
|
|||
disableMismatchWarning,
|
||||
navigationAction,
|
||||
openLink,
|
||||
onBeforePress,
|
||||
],
|
||||
)
|
||||
const hrefAttrs = useMemo(() => {
|
||||
|
@ -274,6 +278,7 @@ interface TextLinkOnWebOnlyProps extends TextProps {
|
|||
title?: string
|
||||
navigationAction?: 'push' | 'replace' | 'navigate'
|
||||
disableMismatchWarning?: boolean
|
||||
onBeforePress?: () => void
|
||||
onPointerEnter?: () => void
|
||||
anchorNoUnderline?: boolean
|
||||
}
|
||||
|
@ -287,6 +292,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
|
|||
lineHeight,
|
||||
navigationAction,
|
||||
disableMismatchWarning,
|
||||
onBeforePress,
|
||||
...props
|
||||
}: TextLinkOnWebOnlyProps) {
|
||||
if (isWeb) {
|
||||
|
@ -302,6 +308,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
|
|||
title={props.title}
|
||||
navigationAction={navigationAction}
|
||||
disableMismatchWarning={disableMismatchWarning}
|
||||
onBeforePress={onBeforePress}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React, {memo} from 'react'
|
||||
import React, {memo, useCallback} from 'react'
|
||||
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
|
||||
import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {usePrefetchProfileQuery} from '#/state/queries/profile'
|
||||
import {precacheProfile, usePrefetchProfileQuery} from '#/state/queries/profile'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
|
@ -40,15 +41,18 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||
? () => prefetchProfileQuery(opts.author.did)
|
||||
: undefined
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const onBeforePress = useCallback(() => {
|
||||
precacheProfile(queryClient, opts.author)
|
||||
}, [queryClient, opts.author])
|
||||
|
||||
return (
|
||||
<View style={[styles.container, opts.style]}>
|
||||
{opts.showAvatar && (
|
||||
<View style={styles.avatar}>
|
||||
<PreviewableUserAvatar
|
||||
size={opts.avatarSize || 16}
|
||||
did={opts.author.did}
|
||||
handle={opts.author.handle}
|
||||
avatar={opts.author.avatar}
|
||||
profile={opts.author}
|
||||
moderation={opts.avatarModeration}
|
||||
type={opts.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
/>
|
||||
|
@ -71,6 +75,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||
</>
|
||||
}
|
||||
href={profileLink}
|
||||
onBeforePress={onBeforePress}
|
||||
onPointerEnter={onPointerEnter}
|
||||
/>
|
||||
<TextLinkOnWebOnly
|
||||
|
@ -79,6 +84,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||
style={[pal.textLight, {flexShrink: 4}]}
|
||||
text={'\xa0' + sanitizeHandle(handle, '@')}
|
||||
href={profileLink}
|
||||
onBeforePress={onBeforePress}
|
||||
onPointerEnter={onPointerEnter}
|
||||
anchorNoUnderline
|
||||
/>
|
||||
|
@ -103,6 +109,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||
title={niceDate(opts.timestamp)}
|
||||
accessibilityHint=""
|
||||
href={opts.postHref}
|
||||
onBeforePress={onBeforePress}
|
||||
/>
|
||||
)}
|
||||
</TimeElapsed>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import {ago} from 'lib/strings/time'
|
||||
|
||||
import {useTickEveryMinute} from '#/state/shell'
|
||||
import {ago} from 'lib/strings/time'
|
||||
|
||||
// FIXME(dan): Figure out why the false positives
|
||||
|
||||
|
@ -12,7 +13,7 @@ export function TimeElapsed({
|
|||
children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element
|
||||
}) {
|
||||
const tick = useTickEveryMinute()
|
||||
const [timeElapsed, setTimeAgo] = React.useState(ago(timestamp))
|
||||
const [timeElapsed, setTimeAgo] = React.useState(() => ago(timestamp))
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeAgo(ago(timestamp))
|
||||
|
|
|
@ -2,10 +2,11 @@ import React, {memo, useMemo} from 'react'
|
|||
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import Svg, {Circle, Path, Rect} from 'react-native-svg'
|
||||
import {ModerationUI} from '@atproto/api'
|
||||
import {AppBskyActorDefs, ModerationUI} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {colors} from 'lib/styles'
|
||||
import {isAndroid, isNative, isWeb} from 'platform/detection'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {HighPriorityImage} from 'view/com/util/images/Image'
|
||||
import {tokens, useTheme} from '#/alf'
|
||||
import {
|
||||
|
@ -47,8 +49,7 @@ interface EditableUserAvatarProps extends BaseUserAvatarProps {
|
|||
|
||||
interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
|
||||
moderation?: ModerationUI
|
||||
did: string
|
||||
handle: string
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
}
|
||||
|
||||
const BLUR_AMOUNT = isWeb ? 5 : 100
|
||||
|
@ -371,19 +372,28 @@ let EditableUserAvatar = ({
|
|||
EditableUserAvatar = memo(EditableUserAvatar)
|
||||
export {EditableUserAvatar}
|
||||
|
||||
let PreviewableUserAvatar = (
|
||||
props: PreviewableUserAvatarProps,
|
||||
): React.ReactNode => {
|
||||
let PreviewableUserAvatar = ({
|
||||
moderation,
|
||||
profile,
|
||||
...rest
|
||||
}: PreviewableUserAvatarProps): React.ReactNode => {
|
||||
const {_} = useLingui()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
precacheProfile(queryClient, profile)
|
||||
}, [profile, queryClient])
|
||||
|
||||
return (
|
||||
<ProfileHoverCard did={props.did}>
|
||||
<ProfileHoverCard did={profile.did}>
|
||||
<Link
|
||||
label={_(msg`See profile`)}
|
||||
to={makeProfileLink({
|
||||
did: props.did,
|
||||
handle: props.handle,
|
||||
})}>
|
||||
<UserAvatar {...props} />
|
||||
did: profile.did,
|
||||
handle: profile.handle,
|
||||
})}
|
||||
onPress={onPress}>
|
||||
<UserAvatar avatar={profile.avatar} moderation={moderation} {...rest} />
|
||||
</Link>
|
||||
</ProfileHoverCard>
|
||||
)
|
||||
|
|
|
@ -26,10 +26,10 @@ import {useQueryClient} from '@tanstack/react-query'
|
|||
import {HITSLOP_20} from '#/lib/constants'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useModerationOpts} from '#/state/queries/preferences'
|
||||
import {RQKEY as RQKEY_URI} from '#/state/queries/resolve-uri'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {InfoCircleIcon} from 'lib/icons'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {precacheProfile} from 'state/queries/profile'
|
||||
import {ComposerOptsQuote} from 'state/shell/composer'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {RichText} from '#/components/RichText'
|
||||
|
@ -149,8 +149,8 @@ export function QuoteEmbed({
|
|||
}, [quote.embeds])
|
||||
|
||||
const onBeforePress = React.useCallback(() => {
|
||||
queryClient.setQueryData(RQKEY_URI(quote.author.handle), quote.author.did)
|
||||
}, [queryClient, quote.author.did, quote.author.handle])
|
||||
precacheProfile(queryClient, quote.author)
|
||||
}, [queryClient, quote.author])
|
||||
|
||||
return (
|
||||
<ContentHider modui={moderation?.ui('contentList')}>
|
||||
|
|
Loading…
Reference in New Issue