Merge branch '3p-moderators' of github.com:bluesky-social/social-app into 3p-moderators
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bsky.app",
|
"name": "bsky.app",
|
||||||
"version": "1.72.0",
|
"version": "1.73.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ function ModerationDetailsDialogInner({
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
name = _(msg`Account Muted`)
|
name = _(msg`Account Muted`)
|
||||||
description = _(msg`You have muted this user.`)
|
description = _(msg`You have muted this account.`)
|
||||||
}
|
}
|
||||||
} else if (modcause.type === 'mute-word') {
|
} else if (modcause.type === 'mute-word') {
|
||||||
name = _(msg`Post Hidden by Muted Word`)
|
name = _(msg`Post Hidden by Muted Word`)
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ export function useModerationCauseDescription(
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
icon: EyeSlash,
|
icon: EyeSlash,
|
||||||
name: _(msg`Muted User`),
|
name: _(msg`Account Muted`),
|
||||||
description: _(msg`You have muted this user`),
|
description: _(msg`You have muted this account.`),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,47 @@
|
|||||||
export type Events = {
|
export type LogEvents = {
|
||||||
init: {
|
init: {
|
||||||
initMs: number
|
initMs: number
|
||||||
}
|
}
|
||||||
|
'feed:endReached': {
|
||||||
|
feedType: string
|
||||||
|
itemCount: number
|
||||||
|
}
|
||||||
|
'post:create': {
|
||||||
|
imageCount: number
|
||||||
|
isReply: boolean
|
||||||
|
hasLink: boolean
|
||||||
|
hasQuote: boolean
|
||||||
|
langs: string
|
||||||
|
logContext: 'Composer'
|
||||||
|
}
|
||||||
|
'post:like': {
|
||||||
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
|
}
|
||||||
|
'post:repost': {
|
||||||
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
|
}
|
||||||
|
'post:unlike': {
|
||||||
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
|
}
|
||||||
|
'post:unrepost': {
|
||||||
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
|
}
|
||||||
|
'profile:follow': {
|
||||||
|
logContext:
|
||||||
|
| 'RecommendedFollowsItem'
|
||||||
|
| 'PostThreadItem'
|
||||||
|
| 'ProfileCard'
|
||||||
|
| 'ProfileHeader'
|
||||||
|
| 'ProfileHeaderSuggestedFollows'
|
||||||
|
| 'ProfileMenu'
|
||||||
|
}
|
||||||
|
'profile:unfollow': {
|
||||||
|
logContext:
|
||||||
|
| 'RecommendedFollowsItem'
|
||||||
|
| 'PostThreadItem'
|
||||||
|
| 'ProfileCard'
|
||||||
|
| 'ProfileHeader'
|
||||||
|
| 'ProfileHeaderSuggestedFollows'
|
||||||
|
| 'ProfileMenu'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import {
|
|||||||
} from 'statsig-react-native-expo'
|
} from 'statsig-react-native-expo'
|
||||||
import {useSession} from '../../state/session'
|
import {useSession} from '../../state/session'
|
||||||
import {sha256} from 'js-sha256'
|
import {sha256} from 'js-sha256'
|
||||||
import {Events} from './events'
|
import {LogEvents} from './events'
|
||||||
|
|
||||||
|
export type {LogEvents}
|
||||||
|
|
||||||
const statsigOptions = {
|
const statsigOptions = {
|
||||||
environment: {
|
environment: {
|
||||||
@@ -31,9 +33,9 @@ export function attachRouteToLogEvents(
|
|||||||
getCurrentRouteName = getRouteName
|
getCurrentRouteName = getRouteName
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logEvent<E extends keyof Events>(
|
export function logEvent<E extends keyof LogEvents>(
|
||||||
eventName: E & string,
|
eventName: E & string,
|
||||||
rawMetadata?: Events[E] & FlatJSONRecord,
|
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
||||||
) {
|
) {
|
||||||
const fullMetadata = {
|
const fullMetadata = {
|
||||||
...rawMetadata,
|
...rawMetadata,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {ProfileHeaderShell} from './Shell'
|
|||||||
import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
|
import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
|
||||||
import {ProfileHeaderDisplayName} from './DisplayName'
|
import {ProfileHeaderDisplayName} from './DisplayName'
|
||||||
import {ProfileHeaderHandle} from './Handle'
|
import {ProfileHeaderHandle} from './Handle'
|
||||||
|
import {ProfileHeaderMetrics} from './Metrics'
|
||||||
import {
|
import {
|
||||||
Heart2_Stroke2_Corner0_Rounded as Heart,
|
Heart2_Stroke2_Corner0_Rounded as Heart,
|
||||||
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
|
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
|
||||||
@@ -65,6 +66,7 @@ let ProfileHeaderLabeler = ({
|
|||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const cantSubscribePrompt = Prompt.usePromptControl()
|
const cantSubscribePrompt = Prompt.usePromptControl()
|
||||||
|
const isSelf = currentAccount?.did === profile.did
|
||||||
|
|
||||||
const moderation = useMemo(
|
const moderation = useMemo(
|
||||||
() => moderateProfile(profile, moderationOpts),
|
() => moderateProfile(profile, moderationOpts),
|
||||||
@@ -233,6 +235,7 @@ let ProfileHeaderLabeler = ({
|
|||||||
</View>
|
</View>
|
||||||
{!isPlaceholderProfile && (
|
{!isPlaceholderProfile && (
|
||||||
<>
|
<>
|
||||||
|
{isSelf && <ProfileHeaderMetrics profile={profile} />}
|
||||||
{descriptionRT && !moderation.ui('profileView').blur ? (
|
{descriptionRT && !moderation.ui('profileView').blur ? (
|
||||||
<View pointerEvents="auto">
|
<View pointerEvents="auto">
|
||||||
<RichText
|
<RichText
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ let ProfileHeaderStandard = ({
|
|||||||
[profile, moderationOpts],
|
[profile, moderationOpts],
|
||||||
)
|
)
|
||||||
const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false)
|
const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false)
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
|
profile,
|
||||||
|
'ProfileHeader',
|
||||||
|
)
|
||||||
const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||||
const unblockPromptControl = Prompt.usePromptControl()
|
const unblockPromptControl = Prompt.usePromptControl()
|
||||||
const requireAuth = useRequireAuth()
|
const requireAuth = useRequireAuth()
|
||||||
|
|||||||
+35
-12
@@ -5,6 +5,7 @@ import {Shadow} from '#/state/cache/types'
|
|||||||
import {getAgent} from '#/state/session'
|
import {getAgent} from '#/state/session'
|
||||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||||
import {track} from '#/lib/analytics/analytics'
|
import {track} from '#/lib/analytics/analytics'
|
||||||
|
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
||||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||||
|
|
||||||
export const RQKEY = (postUri: string) => ['post', postUri]
|
export const RQKEY = (postUri: string) => ['post', postUri]
|
||||||
@@ -58,12 +59,14 @@ export function useGetPost() {
|
|||||||
|
|
||||||
export function usePostLikeMutationQueue(
|
export function usePostLikeMutationQueue(
|
||||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||||
|
logContext: LogEvents['post:like']['logContext'] &
|
||||||
|
LogEvents['post:unlike']['logContext'],
|
||||||
) {
|
) {
|
||||||
const postUri = post.uri
|
const postUri = post.uri
|
||||||
const postCid = post.cid
|
const postCid = post.cid
|
||||||
const initialLikeUri = post.viewer?.like
|
const initialLikeUri = post.viewer?.like
|
||||||
const likeMutation = usePostLikeMutation()
|
const likeMutation = usePostLikeMutation(logContext)
|
||||||
const unlikeMutation = usePostUnlikeMutation()
|
const unlikeMutation = usePostUnlikeMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
initialState: initialLikeUri,
|
initialState: initialLikeUri,
|
||||||
@@ -111,22 +114,30 @@ export function usePostLikeMutationQueue(
|
|||||||
return [queueLike, queueUnlike]
|
return [queueLike, queueUnlike]
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePostLikeMutation() {
|
function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) {
|
||||||
return useMutation<
|
return useMutation<
|
||||||
{uri: string}, // responds with the uri of the like
|
{uri: string}, // responds with the uri of the like
|
||||||
Error,
|
Error,
|
||||||
{uri: string; cid: string} // the post's uri and cid
|
{uri: string; cid: string} // the post's uri and cid
|
||||||
>({
|
>({
|
||||||
mutationFn: post => getAgent().like(post.uri, post.cid),
|
mutationFn: post => {
|
||||||
|
logEvent('post:like', {logContext})
|
||||||
|
return getAgent().like(post.uri, post.cid)
|
||||||
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
track('Post:Like')
|
track('Post:Like')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePostUnlikeMutation() {
|
function usePostUnlikeMutation(
|
||||||
|
logContext: LogEvents['post:unlike']['logContext'],
|
||||||
|
) {
|
||||||
return useMutation<void, Error, {postUri: string; likeUri: string}>({
|
return useMutation<void, Error, {postUri: string; likeUri: string}>({
|
||||||
mutationFn: ({likeUri}) => getAgent().deleteLike(likeUri),
|
mutationFn: ({likeUri}) => {
|
||||||
|
logEvent('post:unlike', {logContext})
|
||||||
|
return getAgent().deleteLike(likeUri)
|
||||||
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
track('Post:Unlike')
|
track('Post:Unlike')
|
||||||
},
|
},
|
||||||
@@ -135,12 +146,14 @@ function usePostUnlikeMutation() {
|
|||||||
|
|
||||||
export function usePostRepostMutationQueue(
|
export function usePostRepostMutationQueue(
|
||||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||||
|
logContext: LogEvents['post:repost']['logContext'] &
|
||||||
|
LogEvents['post:unrepost']['logContext'],
|
||||||
) {
|
) {
|
||||||
const postUri = post.uri
|
const postUri = post.uri
|
||||||
const postCid = post.cid
|
const postCid = post.cid
|
||||||
const initialRepostUri = post.viewer?.repost
|
const initialRepostUri = post.viewer?.repost
|
||||||
const repostMutation = usePostRepostMutation()
|
const repostMutation = usePostRepostMutation(logContext)
|
||||||
const unrepostMutation = usePostUnrepostMutation()
|
const unrepostMutation = usePostUnrepostMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
initialState: initialRepostUri,
|
initialState: initialRepostUri,
|
||||||
@@ -188,22 +201,32 @@ export function usePostRepostMutationQueue(
|
|||||||
return [queueRepost, queueUnrepost]
|
return [queueRepost, queueUnrepost]
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePostRepostMutation() {
|
function usePostRepostMutation(
|
||||||
|
logContext: LogEvents['post:repost']['logContext'],
|
||||||
|
) {
|
||||||
return useMutation<
|
return useMutation<
|
||||||
{uri: string}, // responds with the uri of the repost
|
{uri: string}, // responds with the uri of the repost
|
||||||
Error,
|
Error,
|
||||||
{uri: string; cid: string} // the post's uri and cid
|
{uri: string; cid: string} // the post's uri and cid
|
||||||
>({
|
>({
|
||||||
mutationFn: post => getAgent().repost(post.uri, post.cid),
|
mutationFn: post => {
|
||||||
|
logEvent('post:repost', {logContext})
|
||||||
|
return getAgent().repost(post.uri, post.cid)
|
||||||
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
track('Post:Repost')
|
track('Post:Repost')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePostUnrepostMutation() {
|
function usePostUnrepostMutation(
|
||||||
|
logContext: LogEvents['post:unrepost']['logContext'],
|
||||||
|
) {
|
||||||
return useMutation<void, Error, {postUri: string; repostUri: string}>({
|
return useMutation<void, Error, {postUri: string; repostUri: string}>({
|
||||||
mutationFn: ({repostUri}) => getAgent().deleteRepost(repostUri),
|
mutationFn: ({repostUri}) => {
|
||||||
|
logEvent('post:unrepost', {logContext})
|
||||||
|
return getAgent().deleteRepost(repostUri)
|
||||||
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
track('Post:Unrepost')
|
track('Post:Unrepost')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
|
|||||||
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
|
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {track} from '#/lib/analytics/analytics'
|
import {track} from '#/lib/analytics/analytics'
|
||||||
|
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
||||||
import {ThreadNode} from './post-thread'
|
import {ThreadNode} from './post-thread'
|
||||||
|
|
||||||
export const RQKEY = (did: string) => ['profile', did]
|
export const RQKEY = (did: string) => ['profile', did]
|
||||||
@@ -186,11 +187,13 @@ export function useProfileUpdateMutation() {
|
|||||||
|
|
||||||
export function useProfileFollowMutationQueue(
|
export function useProfileFollowMutationQueue(
|
||||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
|
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
|
||||||
|
logContext: LogEvents['profile:follow']['logContext'] &
|
||||||
|
LogEvents['profile:unfollow']['logContext'],
|
||||||
) {
|
) {
|
||||||
const did = profile.did
|
const did = profile.did
|
||||||
const initialFollowingUri = profile.viewer?.following
|
const initialFollowingUri = profile.viewer?.following
|
||||||
const followMutation = useProfileFollowMutation()
|
const followMutation = useProfileFollowMutation(logContext)
|
||||||
const unfollowMutation = useProfileUnfollowMutation()
|
const unfollowMutation = useProfileUnfollowMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
initialState: initialFollowingUri,
|
initialState: initialFollowingUri,
|
||||||
@@ -237,9 +240,12 @@ export function useProfileFollowMutationQueue(
|
|||||||
return [queueFollow, queueUnfollow]
|
return [queueFollow, queueUnfollow]
|
||||||
}
|
}
|
||||||
|
|
||||||
function useProfileFollowMutation() {
|
function useProfileFollowMutation(
|
||||||
|
logContext: LogEvents['profile:follow']['logContext'],
|
||||||
|
) {
|
||||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||||
mutationFn: async ({did}) => {
|
mutationFn: async ({did}) => {
|
||||||
|
logEvent('profile:follow', {logContext})
|
||||||
return await getAgent().follow(did)
|
return await getAgent().follow(did)
|
||||||
},
|
},
|
||||||
onSuccess(data, variables) {
|
onSuccess(data, variables) {
|
||||||
@@ -248,9 +254,12 @@ function useProfileFollowMutation() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function useProfileUnfollowMutation() {
|
function useProfileUnfollowMutation(
|
||||||
|
logContext: LogEvents['profile:unfollow']['logContext'],
|
||||||
|
) {
|
||||||
return useMutation<void, Error, {did: string; followUri: string}>({
|
return useMutation<void, Error, {did: string; followUri: string}>({
|
||||||
mutationFn: async ({followUri}) => {
|
mutationFn: async ({followUri}) => {
|
||||||
|
logEvent('profile:unfollow', {logContext})
|
||||||
track('Profile:Unfollow', {username: followUri})
|
track('Profile:Unfollow', {username: followUri})
|
||||||
return await getAgent().deleteFollow(followUri)
|
return await getAgent().deleteFollow(followUri)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function RecommendedFollowsItem({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProfileCard({
|
function ProfileCard({
|
||||||
profile,
|
profile,
|
||||||
onFollowStateChange,
|
onFollowStateChange,
|
||||||
moderation,
|
moderation,
|
||||||
@@ -72,7 +72,10 @@ export function ProfileCard({
|
|||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const [addingMoreSuggestions, setAddingMoreSuggestions] =
|
const [addingMoreSuggestions, setAddingMoreSuggestions] =
|
||||||
React.useState(false)
|
React.useState(false)
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
|
profile,
|
||||||
|
'RecommendedFollowsItem',
|
||||||
|
)
|
||||||
|
|
||||||
const onToggleFollow = React.useCallback(async () => {
|
const onToggleFollow = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import {logger} from '#/logger'
|
|||||||
import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo'
|
import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {useDialogStateControlContext} from 'state/dialogs'
|
import {useDialogStateControlContext} from 'state/dialogs'
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
|
|
||||||
type Props = ComposerOpts
|
type Props = ComposerOpts
|
||||||
export const ComposePost = observer(function ComposePost({
|
export const ComposePost = observer(function ComposePost({
|
||||||
@@ -255,6 +256,16 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
return
|
return
|
||||||
} finally {
|
} finally {
|
||||||
|
if (postUri) {
|
||||||
|
logEvent('post:create', {
|
||||||
|
imageCount: gallery.size,
|
||||||
|
isReply: replyTo != null,
|
||||||
|
hasLink: extLink != null,
|
||||||
|
hasQuote: quote != null,
|
||||||
|
langs: langPrefs.postLanguage,
|
||||||
|
logContext: 'Composer',
|
||||||
|
})
|
||||||
|
}
|
||||||
track('Create Post', {
|
track('Create Post', {
|
||||||
imageCount: gallery.size,
|
imageCount: gallery.size,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ function PostThreadFollowBtnLoaded({
|
|||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||||
const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
|
const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
|
||||||
useProfileShadow(profileUnshadowed)
|
useProfileShadow(profileUnshadowed)
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
|
profile,
|
||||||
|
'PostThreadItem',
|
||||||
|
)
|
||||||
const requireAuth = useRequireAuth()
|
const requireAuth = useRequireAuth()
|
||||||
|
|
||||||
const isFollowing = !!profile.viewer?.following
|
const isFollowing = !!profile.viewer?.following
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ let PostThreadItemLoaded = ({
|
|||||||
record={record}
|
record={record}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
|
logContext="PostThreadItem"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -521,6 +522,7 @@ let PostThreadItemLoaded = ({
|
|||||||
record={record}
|
record={record}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
|
logContext="PostThreadItem"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ function PostInner({
|
|||||||
record={record}
|
record={record}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
|
logContext="Post"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
|
import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
|
||||||
import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
|
import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
|
||||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
|
|
||||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
@@ -223,16 +224,29 @@ let Feed = ({
|
|||||||
setIsPTRing(false)
|
setIsPTRing(false)
|
||||||
}, [refetch, track, setIsPTRing, onHasNew])
|
}, [refetch, track, setIsPTRing, onHasNew])
|
||||||
|
|
||||||
|
const feedType = feed.split('|')[0]
|
||||||
const onEndReached = React.useCallback(async () => {
|
const onEndReached = React.useCallback(async () => {
|
||||||
if (isFetching || !hasNextPage || isError) return
|
if (isFetching || !hasNextPage || isError) return
|
||||||
|
|
||||||
|
logEvent('feed:endReached', {
|
||||||
|
feedType: feedType,
|
||||||
|
itemCount: feedItems.length,
|
||||||
|
})
|
||||||
track('Feed:onEndReached')
|
track('Feed:onEndReached')
|
||||||
try {
|
try {
|
||||||
await fetchNextPage()
|
await fetchNextPage()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Failed to load more posts', {message: err})
|
logger.error('Failed to load more posts', {message: err})
|
||||||
}
|
}
|
||||||
}, [isFetching, hasNextPage, isError, fetchNextPage, track])
|
}, [
|
||||||
|
isFetching,
|
||||||
|
hasNextPage,
|
||||||
|
isError,
|
||||||
|
fetchNextPage,
|
||||||
|
track,
|
||||||
|
feedType,
|
||||||
|
feedItems.length,
|
||||||
|
])
|
||||||
|
|
||||||
const onPressTryAgain = React.useCallback(() => {
|
const onPressTryAgain = React.useCallback(() => {
|
||||||
refetch()
|
refetch()
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ let FeedItemInner = ({
|
|||||||
record={record}
|
record={record}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
|
logContext="FeedItem"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -13,13 +13,18 @@ export function FollowButton({
|
|||||||
followedType = 'default',
|
followedType = 'default',
|
||||||
profile,
|
profile,
|
||||||
labelStyle,
|
labelStyle,
|
||||||
|
logContext,
|
||||||
}: {
|
}: {
|
||||||
unfollowedType?: ButtonType
|
unfollowedType?: ButtonType
|
||||||
followedType?: ButtonType
|
followedType?: ButtonType
|
||||||
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||||
labelStyle?: StyleProp<TextStyle>
|
labelStyle?: StyleProp<TextStyle>
|
||||||
|
logContext: 'ProfileCard'
|
||||||
}) {
|
}) {
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
|
profile,
|
||||||
|
logContext,
|
||||||
|
)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const onPressFollow = async () => {
|
const onPressFollow = async () => {
|
||||||
|
|||||||
@@ -256,7 +256,9 @@ export function ProfileCardWithFollowBtn({
|
|||||||
renderButton={
|
renderButton={
|
||||||
isMe
|
isMe
|
||||||
? undefined
|
? undefined
|
||||||
: profileShadow => <FollowButton profile={profileShadow} />
|
: profileShadow => (
|
||||||
|
<FollowButton profile={profileShadow} logContext="ProfileCard" />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -170,7 +170,10 @@ function SuggestedFollow({
|
|||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const profile = useProfileShadow(profileUnshadowed)
|
const profile = useProfileShadow(profileUnshadowed)
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
|
profile,
|
||||||
|
'ProfileHeaderSuggestedFollows',
|
||||||
|
)
|
||||||
|
|
||||||
const onPressFollow = React.useCallback(async () => {
|
const onPressFollow = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -59,9 +59,17 @@ let ProfileMenu = ({
|
|||||||
|
|
||||||
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
|
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
|
||||||
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
|
profile,
|
||||||
|
'ProfileMenu',
|
||||||
|
)
|
||||||
|
|
||||||
const blockPromptControl = Prompt.usePromptControl()
|
const blockPromptControl = Prompt.usePromptControl()
|
||||||
|
const loggedOutWarningPromptControl = Prompt.usePromptControl()
|
||||||
|
|
||||||
|
const showLoggedOutWarning = React.useMemo(() => {
|
||||||
|
return !!profile.labels?.find(label => label.val === '!no-unauthenticated')
|
||||||
|
}, [profile.labels])
|
||||||
|
|
||||||
const invalidateProfileQuery = React.useCallback(() => {
|
const invalidateProfileQuery = React.useCallback(() => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
@@ -205,7 +213,13 @@ let ProfileMenu = ({
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
testID="profileHeaderDropdownShareBtn"
|
testID="profileHeaderDropdownShareBtn"
|
||||||
label={_(msg`Share`)}
|
label={_(msg`Share`)}
|
||||||
onPress={onPressShare}>
|
onPress={() => {
|
||||||
|
if (showLoggedOutWarning) {
|
||||||
|
loggedOutWarningPromptControl.open()
|
||||||
|
} else {
|
||||||
|
onPressShare()
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<Menu.ItemText>
|
<Menu.ItemText>
|
||||||
<Trans>Share</Trans>
|
<Trans>Share</Trans>
|
||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
@@ -348,6 +362,16 @@ let ProfileMenu = ({
|
|||||||
}
|
}
|
||||||
confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'}
|
confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Prompt.Basic
|
||||||
|
control={loggedOutWarningPromptControl}
|
||||||
|
title={_(msg`Note about sharing`)}
|
||||||
|
description={_(
|
||||||
|
msg`This profile is only visible to logged-in users. It won't be visible to people who aren't logged in.`,
|
||||||
|
)}
|
||||||
|
onConfirm={onPressShare}
|
||||||
|
confirmButtonCta={_(msg`Share anyway`)}
|
||||||
|
/>
|
||||||
</EventStopper>
|
</EventStopper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,11 +82,13 @@ let PostDropdownBtn = ({
|
|||||||
const reportDialogControl = useReportDialogControl()
|
const reportDialogControl = useReportDialogControl()
|
||||||
const deletePromptControl = useDialogControl()
|
const deletePromptControl = useDialogControl()
|
||||||
const hidePromptControl = useDialogControl()
|
const hidePromptControl = useDialogControl()
|
||||||
|
const loggedOutWarningPromptControl = useDialogControl()
|
||||||
|
|
||||||
const rootUri = record.reply?.root?.uri || postUri
|
const rootUri = record.reply?.root?.uri || postUri
|
||||||
const isThreadMuted = mutedThreads.includes(rootUri)
|
const isThreadMuted = mutedThreads.includes(rootUri)
|
||||||
const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri)
|
const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri)
|
||||||
const isAuthor = postAuthor.did === currentAccount?.did
|
const isAuthor = postAuthor.did === currentAccount?.did
|
||||||
|
|
||||||
const href = React.useMemo(() => {
|
const href = React.useMemo(() => {
|
||||||
const urip = new AtUri(postUri)
|
const urip = new AtUri(postUri)
|
||||||
return makeProfileLink(postAuthor, 'post', urip.rkey)
|
return makeProfileLink(postAuthor, 'post', urip.rkey)
|
||||||
@@ -164,6 +166,17 @@ let PostDropdownBtn = ({
|
|||||||
hidePost({uri: postUri})
|
hidePost({uri: postUri})
|
||||||
}, [postUri, hidePost])
|
}, [postUri, hidePost])
|
||||||
|
|
||||||
|
const shouldShowLoggedOutWarning = React.useMemo(() => {
|
||||||
|
return !!postAuthor.labels?.find(
|
||||||
|
label => label.val === '!no-unauthenticated',
|
||||||
|
)
|
||||||
|
}, [postAuthor])
|
||||||
|
|
||||||
|
const onSharePost = React.useCallback(() => {
|
||||||
|
const url = toShareUrl(href)
|
||||||
|
shareUrl(url)
|
||||||
|
}, [href])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventStopper onKeyDown={false}>
|
<EventStopper onKeyDown={false}>
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
@@ -214,8 +227,11 @@ let PostDropdownBtn = ({
|
|||||||
testID="postDropdownShareBtn"
|
testID="postDropdownShareBtn"
|
||||||
label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
|
label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const url = toShareUrl(href)
|
if (shouldShowLoggedOutWarning) {
|
||||||
shareUrl(url)
|
loggedOutWarningPromptControl.open()
|
||||||
|
} else {
|
||||||
|
onSharePost()
|
||||||
|
}
|
||||||
}}>
|
}}>
|
||||||
<Menu.ItemText>
|
<Menu.ItemText>
|
||||||
{isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
|
{isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
|
||||||
@@ -320,6 +336,16 @@ let PostDropdownBtn = ({
|
|||||||
cid: postCid,
|
cid: postCid,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Prompt.Basic
|
||||||
|
control={loggedOutWarningPromptControl}
|
||||||
|
title={_(msg`Note about sharing`)}
|
||||||
|
description={_(
|
||||||
|
msg`This post is only visible to logged-in users. It won't be visible to people who aren't logged in.`,
|
||||||
|
)}
|
||||||
|
onConfirm={onSharePost}
|
||||||
|
confirmButtonCta={_(msg`Share anyway`)}
|
||||||
|
/>
|
||||||
</EventStopper>
|
</EventStopper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ let PostCtrls = ({
|
|||||||
richText,
|
richText,
|
||||||
style,
|
style,
|
||||||
onPressReply,
|
onPressReply,
|
||||||
|
logContext,
|
||||||
}: {
|
}: {
|
||||||
big?: boolean
|
big?: boolean
|
||||||
post: Shadow<AppBskyFeedDefs.PostView>
|
post: Shadow<AppBskyFeedDefs.PostView>
|
||||||
@@ -50,13 +51,17 @@ let PostCtrls = ({
|
|||||||
richText: RichTextAPI
|
richText: RichTextAPI
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
onPressReply: () => void
|
onPressReply: () => void
|
||||||
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
const {closeModal} = useModalControls()
|
const {closeModal} = useModalControls()
|
||||||
const [queueLike, queueUnlike] = usePostLikeMutationQueue(post)
|
const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
|
||||||
const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(post)
|
const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
|
||||||
|
post,
|
||||||
|
logContext,
|
||||||
|
)
|
||||||
const requireAuth = useRequireAuth()
|
const requireAuth = useRequireAuth()
|
||||||
|
|
||||||
const defaultCtrlColor = React.useMemo(
|
const defaultCtrlColor = React.useMemo(
|
||||||
|
|||||||
Reference in New Issue
Block a user