Merge remote-tracking branch 'origin/main' into eric/app-1164-threads-v2-integrate-frontend

* origin/main:
  Post source handling updates (#8472)
  Fix using screen names in `Link` (#8473)
This commit is contained in:
Eric Bailey
2025-06-11 14:18:42 -05:00
12 changed files with 163 additions and 110 deletions
+10 -13
View File
@@ -58,7 +58,6 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies' import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {Provider as UnstablePostSourceProvider} from '#/state/unstable-post-source'
import {TestCtrls} from '#/view/com/testing/TestCtrls' import {TestCtrls} from '#/view/com/testing/TestCtrls'
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext' import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
@@ -152,18 +151,16 @@ function InnerApp() {
<MutedThreadsProvider> <MutedThreadsProvider>
<ProgressGuideProvider> <ProgressGuideProvider>
<ServiceAccountManager> <ServiceAccountManager>
<UnstablePostSourceProvider> <HideBottomBarBorderProvider>
<HideBottomBarBorderProvider> <GestureHandlerRootView
<GestureHandlerRootView style={s.h100pct}>
style={s.h100pct}> <IntentDialogProvider>
<IntentDialogProvider> <TestCtrls />
<TestCtrls /> <Shell />
<Shell /> <NuxDialogs />
<NuxDialogs /> </IntentDialogProvider>
</IntentDialogProvider> </GestureHandlerRootView>
</GestureHandlerRootView> </HideBottomBarBorderProvider>
</HideBottomBarBorderProvider>
</UnstablePostSourceProvider>
</ServiceAccountManager> </ServiceAccountManager>
</ProgressGuideProvider> </ProgressGuideProvider>
</MutedThreadsProvider> </MutedThreadsProvider>
+6 -9
View File
@@ -48,7 +48,6 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies' import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {Provider as UnstablePostSourceProvider} from '#/state/unstable-post-source'
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext' import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext'
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext' import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
@@ -133,14 +132,12 @@ function InnerApp() {
<SafeAreaProvider> <SafeAreaProvider>
<ProgressGuideProvider> <ProgressGuideProvider>
<ServiceConfigProvider> <ServiceConfigProvider>
<UnstablePostSourceProvider> <HideBottomBarBorderProvider>
<HideBottomBarBorderProvider> <IntentDialogProvider>
<IntentDialogProvider> <Shell />
<Shell /> <NuxDialogs />
<NuxDialogs /> </IntentDialogProvider>
</IntentDialogProvider> </HideBottomBarBorderProvider>
</HideBottomBarBorderProvider>
</UnstablePostSourceProvider>
</ServiceConfigProvider> </ServiceConfigProvider>
</ProgressGuideProvider> </ProgressGuideProvider>
</SafeAreaProvider> </SafeAreaProvider>
+3 -5
View File
@@ -4,7 +4,6 @@ import {sanitizeUrl} from '@braintree/sanitize-url'
import { import {
type LinkProps as RNLinkProps, type LinkProps as RNLinkProps,
StackActions, StackActions,
useLinkBuilder,
} from '@react-navigation/native' } from '@react-navigation/native'
import {BSKY_DOWNLOAD_URL} from '#/lib/constants' import {BSKY_DOWNLOAD_URL} from '#/lib/constants'
@@ -95,20 +94,19 @@ export function useLink({
shouldProxy?: boolean shouldProxy?: boolean
}) { }) {
const navigation = useNavigationDeduped() const navigation = useNavigationDeduped()
const {buildHref} = useLinkBuilder()
const href = useMemo(() => { const href = useMemo(() => {
return typeof to === 'string' return typeof to === 'string'
? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) ? convertBskyAppUrlIfNeeded(sanitizeUrl(to))
: to.screen : to.screen
? buildHref(to.screen, to.params) ? router.matchName(to.screen)?.build(to.params)
: to.href : to.href
? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href)) ? convertBskyAppUrlIfNeeded(sanitizeUrl(to.href))
: undefined : undefined
}, [to, buildHref]) }, [to])
if (!href) { if (!href) {
throw new Error( throw new Error(
'Link `to` prop must be a string or an object with `screen` and `params` properties', 'Could not resolve screen. Link `to` prop must be a string or an object with `screen` and `params` properties',
) )
} }
+4 -4
View File
@@ -1,4 +1,4 @@
import {Route, RouteParams} from './types' import {type Route, type RouteParams} from './types'
export class Router { export class Router {
routes: [string, Route][] = [] routes: [string, Route][] = []
@@ -45,7 +45,7 @@ function createRoute(pattern: string): Route {
}) })
const matcherRe = new RegExp(`^${matcherReInternal}([?]|$)`, 'i') const matcherRe = new RegExp(`^${matcherReInternal}([?]|$)`, 'i')
return { return {
match(path: string) { match(path) {
const {pathname, searchParams} = new URL(path, 'http://throwaway.com') const {pathname, searchParams} = new URL(path, 'http://throwaway.com')
const addedParams = Object.fromEntries(searchParams.entries()) const addedParams = Object.fromEntries(searchParams.entries())
@@ -55,10 +55,10 @@ function createRoute(pattern: string): Route {
} }
return undefined return undefined
}, },
build(params: Record<string, string>) { build(params = {}) {
const str = pattern.replace( const str = pattern.replace(
/:([\w]+)/g, /:([\w]+)/g,
(_m, name) => params[name] || 'undefined', (_m, name) => params[encodeURIComponent(name)] || 'undefined',
) )
let hasQp = false let hasQp = false
+1 -1
View File
@@ -143,5 +143,5 @@ export type RouteParams = Record<string, string>
export type MatchResult = {params: RouteParams} export type MatchResult = {params: RouteParams}
export type Route = { export type Route = {
match: (path: string) => MatchResult | undefined match: (path: string) => MatchResult | undefined
build: (params: RouteParams) => string build: (params?: Record<string, any>) => string
} }
+2
View File
@@ -10,6 +10,8 @@ export enum LogContext {
ConversationAgent = 'conversation-agent', ConversationAgent = 'conversation-agent',
DMsAgent = 'dms-agent', DMsAgent = 'dms-agent',
ReportDialog = 'report-dialog', ReportDialog = 'report-dialog',
FeedFeedback = 'feed-feedback',
PostSource = 'post-source',
/** /**
* METRIC IS FOR INTERNAL USE ONLY, don't create any other loggers using this * METRIC IS FOR INTERNAL USE ONLY, don't create any other loggers using this
+7 -1
View File
@@ -12,7 +12,7 @@ import throttle from 'lodash.throttle'
import {FEEDBACK_FEEDS, STAGING_FEEDS} from '#/lib/constants' import {FEEDBACK_FEEDS, STAGING_FEEDS} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {Logger} from '#/logger'
import { import {
type FeedDescriptor, type FeedDescriptor,
type FeedPostSliceItem, type FeedPostSliceItem,
@@ -20,6 +20,8 @@ import {
import {getItemsForFeedback} from '#/view/com/posts/PostFeed' import {getItemsForFeedback} from '#/view/com/posts/PostFeed'
import {useAgent} from './session' import {useAgent} from './session'
const logger = Logger.create(Logger.Context.FeedFeedback)
export type StateContext = { export type StateContext = {
enabled: boolean enabled: boolean
onItemSeen: (item: any) => void onItemSeen: (item: any) => void
@@ -89,6 +91,7 @@ export function useFeedFeedback(
} }
sendOrAggregateInteractionsForStats(aggregatedStats.current, interactions) sendOrAggregateInteractionsForStats(aggregatedStats.current, interactions)
throttledFlushAggregatedStats() throttledFlushAggregatedStats()
logger.debug('flushed')
}, [agent, throttledFlushAggregatedStats, feed]) }, [agent, throttledFlushAggregatedStats, feed])
const sendToFeed = useMemo( const sendToFeed = useMemo(
@@ -141,6 +144,9 @@ export function useFeedFeedback(
if (!enabled) { if (!enabled) {
return return
} }
logger.debug('sendInteraction', {
...interaction,
})
if (!history.current.has(interaction)) { if (!history.current.has(interaction)) {
history.current.add(interaction) history.current.add(interaction)
queue.current.add(toString(interaction)) queue.current.add(toString(interaction))
+78 -43
View File
@@ -1,62 +1,97 @@
import {createContext, useCallback, useContext, useRef, useState} from 'react' import {useEffect, useId, useState} from 'react'
import {type AppBskyFeedDefs} from '@atproto/api' import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {type FeedDescriptor} from './queries/post-feed' import {Logger} from '#/logger'
import {type FeedDescriptor} from '#/state/queries/post-feed'
/** /**
* For passing the source of the post (i.e. the original post, from the feed) to the threadview, * Separate logger for better debugging
* without using query params. Deliberately unstable to avoid using query params, use for FeedFeedback
* and other ephemeral non-critical systems.
*/ */
const logger = Logger.create(Logger.Context.PostSource)
type Source = { export type PostSource = {
post: AppBskyFeedDefs.FeedViewPost post: AppBskyFeedDefs.FeedViewPost
feed?: FeedDescriptor feed?: FeedDescriptor
} }
const SetUnstablePostSourceContext = createContext< /**
(key: string, source: Source) => void * A cache of sources that will be consumed by the post thread view. This is
>(() => {}) * cleaned up any time a source is consumed.
const ConsumeUnstablePostSourceContext = createContext< */
(uri: string) => Source | undefined const transientSources = new Map<string, PostSource>()
>(() => undefined)
export function Provider({children}: {children: React.ReactNode}) { /**
const sourcesRef = useRef<Map<string, Source>>(new Map()) * A cache of sources that have been consumed by the post thread view. This is
* not cleaned up, but because we use a new ID for each post thread view that
* consumes a source, this is never reused unless a user navigates back to a
* post thread view that has not been dropped from memory.
*/
const consumedSources = new Map<string, PostSource>()
const setUnstablePostSource = useCallback((key: string, source: Source) => { /**
sourcesRef.current.set(key, source) * For stashing the feed that the user was browsing when they clicked on a post.
}, []) *
* Used for FeedFeedback and other ephemeral non-critical systems.
const consumeUnstablePostSource = useCallback((uri: string) => { */
const source = sourcesRef.current.get(uri) export function setUnstablePostSource(key: string, source: PostSource) {
if (source) { assertValid(
sourcesRef.current.delete(uri) key,
} `setUnstablePostSource key should be a URI containing a handle, received ${key} — use buildPostSourceKey`,
return source
}, [])
return (
<SetUnstablePostSourceContext.Provider value={setUnstablePostSource}>
<ConsumeUnstablePostSourceContext.Provider
value={consumeUnstablePostSource}>
{children}
</ConsumeUnstablePostSourceContext.Provider>
</SetUnstablePostSourceContext.Provider>
) )
} logger.debug('set', {key, source})
transientSources.set(key, source)
export function useSetUnstablePostSource() {
return useContext(SetUnstablePostSourceContext)
} }
/** /**
* DANGER - This hook is unstable and should only be used for FeedFeedback * This hook is unstable and should only be used for FeedFeedback and other
* and other ephemeral non-critical systems. Does not change when the URI changes. * ephemeral non-critical systems. Views that use this hook will continue to
* return a reference to the same source until those views are dropped from
* memory.
*/ */
export function useUnstablePostSource(uri: string) { export function useUnstablePostSource(key: string) {
const consume = useContext(ConsumeUnstablePostSourceContext) const id = useId()
const [source] = useState(() => {
assertValid(
key,
`consumeUnstablePostSource key should be a URI containing a handle, received ${key} — use buildPostSourceKey`,
)
const source = consumedSources.get(id) || transientSources.get(key)
if (source) {
logger.debug('consume', {id, key, source})
transientSources.delete(key)
consumedSources.set(id, source)
}
return source
})
useEffect(() => {
return () => {
consumedSources.delete(id)
logger.debug('cleanup', {id})
}
}, [id])
const [source] = useState(() => consume(uri))
return source return source
} }
/**
* Builds a post source key. This (atm) is a URI where the `host` is the post
* author's handle, not DID.
*/
export function buildPostSourceKey(key: string, handle: string) {
const urip = new AtUri(key)
urip.host = handle
return urip.toString()
}
/**
* Just a lil dev helper
*/
function assertValid(key: string, message: string) {
if (__DEV__) {
const urip = new AtUri(key)
if (urip.host.startsWith('did:')) {
throw new Error(message)
}
}
}
+3 -11
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
@@ -56,13 +55,8 @@ export function HomeHeaderLayoutMobile({
<PressableScale <PressableScale
targetScale={0.9} targetScale={0.9}
onPress={() => { onPress={() => {
emitSoftReset()
}}
onPressIn={() => {
playHaptic('Heavy')
}}
onPressOut={() => {
playHaptic('Light') playHaptic('Light')
emitSoftReset()
}}> }}>
<Logo width={30} /> <Logo width={30} />
</PressableScale> </PressableScale>
@@ -72,7 +66,7 @@ export function HomeHeaderLayoutMobile({
{hasSession && ( {hasSession && (
<Link <Link
testID="viewHeaderHomeFeedPrefsBtn" testID="viewHeaderHomeFeedPrefsBtn"
to="/feeds" to={{screen: 'Feeds'}}
hitSlop={HITSLOP_10} hitSlop={HITSLOP_10}
label={_(msg`View your feeds and explore more`)} label={_(msg`View your feeds and explore more`)}
size="small" size="small"
@@ -81,9 +75,7 @@ export function HomeHeaderLayoutMobile({
shape="square" shape="square"
style={[ style={[
a.justify_center, a.justify_center,
{ {marginRight: -Layout.BUTTON_VISUAL_ALIGNMENT_OFFSET},
marginRight: -Layout.BUTTON_VISUAL_ALIGNMENT_OFFSET,
},
]}> ]}>
<ButtonIcon icon={FeedsIcon} size="lg" /> <ButtonIcon icon={FeedsIcon} size="lg" />
</Link> </Link>
+25 -5
View File
@@ -19,6 +19,7 @@ import {ScrollProvider} from '#/lib/ScrollContext'
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {isAndroid, isNative, isWeb} from '#/platform/detection' import {isAndroid, isNative, isWeb} from '#/platform/detection'
import {useFeedFeedback} from '#/state/feed-feedback'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import { import {
fillThreadModerationCache, fillThreadModerationCache,
@@ -35,6 +36,7 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {useUnstablePostSource} from '#/state/unstable-post-source'
import {List, type ListMethods} from '#/view/com/util/List' import {List, type ListMethods} from '#/view/com/util/List'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
@@ -91,7 +93,7 @@ const keyExtractor = (item: RowItem) => {
return item._reactKey return item._reactKey
} }
export function PostThread({uri}: {uri: string | undefined}) { export function PostThread({uri}: {uri: string}) {
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
@@ -102,6 +104,8 @@ export function PostThread({uri}: {uri: string | undefined}) {
HiddenRepliesState.Hide, HiddenRepliesState.Hide,
) )
const headerRef = React.useRef<View | null>(null) const headerRef = React.useRef<View | null>(null)
const anchorPostSource = useUnstablePostSource(uri)
const feedFeedback = useFeedFeedback(anchorPostSource?.feed, hasSession)
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const { const {
@@ -396,10 +400,18 @@ export function PostThread({uri}: {uri: string | undefined}) {
) )
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const onPressReply = React.useCallback(() => { const onReplyToAnchor = React.useCallback(() => {
if (thread?.type !== 'post') { if (thread?.type !== 'post') {
return return
} }
if (anchorPostSource) {
feedFeedback.sendInteraction({
item: thread.post.uri,
event: 'app.bsky.feed.defs#interactionReply',
feedContext: anchorPostSource.post.feedContext,
reqId: anchorPostSource.post.reqId,
})
}
openComposer({ openComposer({
replyTo: { replyTo: {
uri: thread.post.uri, uri: thread.post.uri,
@@ -411,7 +423,14 @@ export function PostThread({uri}: {uri: string | undefined}) {
}, },
onPost: onPostReply, onPost: onPostReply,
}) })
}, [openComposer, thread, onPostReply, threadModerationCache]) }, [
openComposer,
thread,
onPostReply,
threadModerationCache,
anchorPostSource,
feedFeedback,
])
const canReply = !error && rootPost && !rootPost.viewer?.replyDisabled const canReply = !error && rootPost && !rootPost.viewer?.replyDisabled
const hasParents = const hasParents =
@@ -424,7 +443,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
return ( return (
<View> <View>
{!isMobile && ( {!isMobile && (
<PostThreadComposePrompt onPressCompose={onPressReply} /> <PostThreadComposePrompt onPressCompose={onReplyToAnchor} />
)} )}
</View> </View>
) )
@@ -512,6 +531,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
} }
onPostReply={onPostReply} onPostReply={onPostReply}
hideTopBorder={index === 0 && !item.ctx.isParentLoading} hideTopBorder={index === 0 && !item.ctx.isParentLoading}
anchorPostSource={anchorPostSource}
/> />
</View> </View>
) )
@@ -590,7 +610,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
/> />
</ScrollProvider> </ScrollProvider>
{isMobile && canReply && hasSession && ( {isMobile && canReply && hasSession && (
<MobileComposePrompt onPressReply={onPressReply} /> <MobileComposePrompt onPressReply={onReplyToAnchor} />
)} )}
</> </>
) )
+19 -15
View File
@@ -41,7 +41,7 @@ import {type ThreadPost} from '#/state/queries/post-thread'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {type OnPostSuccessData} from '#/state/shell/composer' import {type OnPostSuccessData} from '#/state/shell/composer'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {useUnstablePostSource} from '#/state/unstable-post-source' import {type PostSource} from '#/state/unstable-post-source'
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn' import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {Link, TextLink} from '#/view/com/util/Link' import {Link, TextLink} from '#/view/com/util/Link'
@@ -89,6 +89,7 @@ export function PostThreadItem({
onPostSuccess, onPostSuccess,
hideTopBorder, hideTopBorder,
threadgateRecord, threadgateRecord,
anchorPostSource,
}: { }: {
post: AppBskyFeedDefs.PostView post: AppBskyFeedDefs.PostView
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
@@ -107,6 +108,7 @@ export function PostThreadItem({
onPostSuccess?: (data: OnPostSuccessData) => void onPostSuccess?: (data: OnPostSuccessData) => void
hideTopBorder?: boolean hideTopBorder?: boolean
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
anchorPostSource?: PostSource
}) { }) {
const postShadowed = usePostShadow(post) const postShadowed = usePostShadow(post)
const richText = useMemo( const richText = useMemo(
@@ -143,6 +145,7 @@ export function PostThreadItem({
onPostSuccess={onPostSuccess} onPostSuccess={onPostSuccess}
hideTopBorder={hideTopBorder} hideTopBorder={hideTopBorder}
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
anchorPostSource={anchorPostSource}
/> />
) )
} }
@@ -189,6 +192,7 @@ let PostThreadItemLoaded = ({
onPostSuccess, onPostSuccess,
hideTopBorder, hideTopBorder,
threadgateRecord, threadgateRecord,
anchorPostSource,
}: { }: {
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
@@ -208,10 +212,10 @@ let PostThreadItemLoaded = ({
onPostSuccess?: (data: OnPostSuccessData) => void onPostSuccess?: (data: OnPostSuccessData) => void
hideTopBorder?: boolean hideTopBorder?: boolean
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
anchorPostSource?: PostSource
}): React.ReactNode => { }): React.ReactNode => {
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
const source = useUnstablePostSource(post.uri) const feedFeedback = useFeedFeedback(anchorPostSource?.feed, hasSession)
const feedFeedback = useFeedFeedback(source?.feed, hasSession)
const t = useTheme() const t = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
@@ -282,12 +286,12 @@ let PostThreadItemLoaded = ({
) )
const onPressReply = () => { const onPressReply = () => {
if (source) { if (anchorPostSource && isHighlightedPost) {
feedFeedback.sendInteraction({ feedFeedback.sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#interactionReply', event: 'app.bsky.feed.defs#interactionReply',
feedContext: source.post.feedContext, feedContext: anchorPostSource.post.feedContext,
reqId: source.post.reqId, reqId: anchorPostSource.post.reqId,
}) })
} }
openComposer({ openComposer({
@@ -305,23 +309,23 @@ let PostThreadItemLoaded = ({
} }
const onOpenAuthor = () => { const onOpenAuthor = () => {
if (source) { if (anchorPostSource) {
feedFeedback.sendInteraction({ feedFeedback.sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#clickthroughAuthor', event: 'app.bsky.feed.defs#clickthroughAuthor',
feedContext: source.post.feedContext, feedContext: anchorPostSource.post.feedContext,
reqId: source.post.reqId, reqId: anchorPostSource.post.reqId,
}) })
} }
} }
const onOpenEmbed = () => { const onOpenEmbed = () => {
if (source) { if (anchorPostSource) {
feedFeedback.sendInteraction({ feedFeedback.sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#clickthroughEmbed', event: 'app.bsky.feed.defs#clickthroughEmbed',
feedContext: source.post.feedContext, feedContext: anchorPostSource.post.feedContext,
reqId: source.post.reqId, reqId: anchorPostSource.post.reqId,
}) })
} }
} }
@@ -332,7 +336,7 @@ let PostThreadItemLoaded = ({
const {isActive: live} = useActorStatus(post.author) const {isActive: live} = useActorStatus(post.author)
const reason = source?.post.reason const reason = anchorPostSource?.post.reason
const viaRepost = useMemo(() => { const viaRepost = useMemo(() => {
if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) { if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) {
return { return {
@@ -557,8 +561,8 @@ let PostThreadItemLoaded = ({
onPostReply={onPostReply} onPostReply={onPostReply}
logContext="PostThreadItem" logContext="PostThreadItem"
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
feedContext={source?.post?.feedContext} feedContext={anchorPostSource?.post?.feedContext}
reqId={source?.post?.reqId} reqId={anchorPostSource?.post?.reqId}
viaRepost={viaRepost} viaRepost={viaRepost}
/> />
</FeedFeedbackProvider> </FeedFeedbackProvider>
+5 -3
View File
@@ -36,7 +36,10 @@ import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {unstableCacheProfileView} from '#/state/queries/profile' import {unstableCacheProfileView} from '#/state/queries/profile'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
import {useSetUnstablePostSource} from '#/state/unstable-post-source' import {
buildPostSourceKey,
setUnstablePostSource,
} from '#/state/unstable-post-source'
import {FeedNameText} from '#/view/com/util/FeedInfoText' import {FeedNameText} from '#/view/com/util/FeedInfoText'
import {Link, TextLink, TextLinkOnWebOnly} from '#/view/com/util/Link' import {Link, TextLink, TextLinkOnWebOnly} from '#/view/com/util/Link'
import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds' import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
@@ -176,7 +179,6 @@ let FeedItemInner = ({
return makeProfileLink(post.author, 'post', urip.rkey) return makeProfileLink(post.author, 'post', urip.rkey)
}, [post.uri, post.author]) }, [post.uri, post.author])
const {sendInteraction, feedDescriptor} = useFeedFeedbackContext() const {sendInteraction, feedDescriptor} = useFeedFeedbackContext()
const unstableSetPostSource = useSetUnstablePostSource()
const onPressReply = () => { const onPressReply = () => {
sendInteraction({ sendInteraction({
@@ -232,7 +234,7 @@ let FeedItemInner = ({
reqId, reqId,
}) })
unstableCacheProfileView(queryClient, post.author) unstableCacheProfileView(queryClient, post.author)
unstableSetPostSource(post.uri, { setUnstablePostSource(buildPostSourceKey(post.uri, post.author.handle), {
feed: feedDescriptor, feed: feedDescriptor,
post: { post: {
post, post,