Send FeedFeedback interactions in thread view (#8414)
This commit is contained in:
parent
665a0430a3
commit
cf63c2ca07
@ -69,7 +69,7 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.15.8",
|
||||
"@atproto/api": "^0.15.9",
|
||||
"@bitdrift/react-native": "^0.6.8",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||
@ -219,7 +219,7 @@
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@atproto/dev-env": "^0.3.132",
|
||||
"@atproto/dev-env": "^0.3.133",
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/runtime": "^7.26.0",
|
||||
|
@ -58,6 +58,7 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||
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 {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
@ -150,14 +151,16 @@ function InnerApp() {
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceAccountManager>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</GestureHandlerRootView>
|
||||
<UnstablePostSourceProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</GestureHandlerRootView>
|
||||
</UnstablePostSourceProvider>
|
||||
</ServiceAccountManager>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
|
@ -48,6 +48,7 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||
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 VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
@ -131,10 +132,12 @@ function InnerApp() {
|
||||
<SafeAreaProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceConfigProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
<UnstablePostSourceProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
</IntentDialogProvider>
|
||||
</UnstablePostSourceProvider>
|
||||
</ServiceConfigProvider>
|
||||
</ProgressGuideProvider>
|
||||
</SafeAreaProvider>
|
||||
|
@ -50,6 +50,7 @@ let PostControls = ({
|
||||
logContext,
|
||||
threadgateRecord,
|
||||
onShowLess,
|
||||
viaRepost,
|
||||
}: {
|
||||
big?: boolean
|
||||
post: Shadow<AppBskyFeedDefs.PostView>
|
||||
@ -63,13 +64,19 @@ let PostControls = ({
|
||||
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
|
||||
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
|
||||
viaRepost?: {uri: string; cid: string}
|
||||
}): React.ReactNode => {
|
||||
const {_, i18n} = useLingui()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
|
||||
const [queueLike, queueUnlike] = usePostLikeMutationQueue(
|
||||
post,
|
||||
viaRepost,
|
||||
logContext,
|
||||
)
|
||||
const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
|
||||
post,
|
||||
viaRepost,
|
||||
logContext,
|
||||
)
|
||||
const requireAuth = useRequireAuth()
|
||||
|
@ -1023,7 +1023,12 @@ function PlayPauseTapArea({
|
||||
const {_} = useLingui()
|
||||
const doubleTapRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const playHaptic = useHaptics()
|
||||
const [queueLike] = usePostLikeMutationQueue(post, 'ImmersiveVideo')
|
||||
// TODO: implement viaRepost -sfn
|
||||
const [queueLike] = usePostLikeMutationQueue(
|
||||
post,
|
||||
undefined,
|
||||
'ImmersiveVideo',
|
||||
)
|
||||
const {sendInteraction} = useFeedFeedbackContext()
|
||||
const {isPlaying} = useEvent(player, 'playingChange', {
|
||||
isPlaying: player.playing,
|
||||
|
@ -1,4 +1,11 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import {AppState, type AppStateStatus} from 'react-native'
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import throttle from 'lodash.throttle'
|
||||
@ -13,31 +20,36 @@ import {
|
||||
import {getItemsForFeedback} from '#/view/com/posts/PostFeed'
|
||||
import {useAgent} from './session'
|
||||
|
||||
type StateContext = {
|
||||
export type StateContext = {
|
||||
enabled: boolean
|
||||
onItemSeen: (item: any) => void
|
||||
sendInteraction: (interaction: AppBskyFeedDefs.Interaction) => void
|
||||
feedDescriptor: FeedDescriptor | undefined
|
||||
}
|
||||
|
||||
const stateContext = React.createContext<StateContext>({
|
||||
const stateContext = createContext<StateContext>({
|
||||
enabled: false,
|
||||
onItemSeen: (_item: any) => {},
|
||||
sendInteraction: (_interaction: AppBskyFeedDefs.Interaction) => {},
|
||||
feedDescriptor: undefined,
|
||||
})
|
||||
|
||||
export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
export function useFeedFeedback(
|
||||
feed: FeedDescriptor | undefined,
|
||||
hasSession: boolean,
|
||||
) {
|
||||
const agent = useAgent()
|
||||
const enabled = isDiscoverFeed(feed) && hasSession
|
||||
|
||||
const queue = React.useRef<Set<string>>(new Set())
|
||||
const history = React.useRef<
|
||||
const queue = useRef<Set<string>>(new Set())
|
||||
const history = useRef<
|
||||
// Use a WeakSet so that we don't need to clear it.
|
||||
// This assumes that referential identity of slice items maps 1:1 to feed (re)fetches.
|
||||
WeakSet<FeedPostSliceItem | AppBskyFeedDefs.Interaction>
|
||||
>(new WeakSet())
|
||||
|
||||
const aggregatedStats = React.useRef<AggregatedStats | null>(null)
|
||||
const throttledFlushAggregatedStats = React.useMemo(
|
||||
const aggregatedStats = useRef<AggregatedStats | null>(null)
|
||||
const throttledFlushAggregatedStats = useMemo(
|
||||
() =>
|
||||
throttle(() => flushToStatsig(aggregatedStats.current), 45e3, {
|
||||
leading: true, // The outer call is already throttled somewhat.
|
||||
@ -46,12 +58,12 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
[],
|
||||
)
|
||||
|
||||
const sendToFeedNoDelay = React.useCallback(() => {
|
||||
const sendToFeedNoDelay = useCallback(() => {
|
||||
const interactions = Array.from(queue.current).map(toInteraction)
|
||||
queue.current.clear()
|
||||
|
||||
let proxyDid = 'did:web:discover.bsky.app'
|
||||
if (STAGING_FEEDS.includes(feed)) {
|
||||
if (STAGING_FEEDS.includes(feed ?? '')) {
|
||||
proxyDid = 'did:web:algo.pop2.bsky.app'
|
||||
}
|
||||
|
||||
@ -79,7 +91,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
throttledFlushAggregatedStats()
|
||||
}, [agent, throttledFlushAggregatedStats, feed])
|
||||
|
||||
const sendToFeed = React.useMemo(
|
||||
const sendToFeed = useMemo(
|
||||
() =>
|
||||
throttle(sendToFeedNoDelay, 10e3, {
|
||||
leading: false,
|
||||
@ -88,7 +100,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
[sendToFeedNoDelay],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
return
|
||||
}
|
||||
@ -100,7 +112,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
return () => sub.remove()
|
||||
}, [enabled, sendToFeed])
|
||||
|
||||
const onItemSeen = React.useCallback(
|
||||
const onItemSeen = useCallback(
|
||||
(feedItem: any) => {
|
||||
if (!enabled) {
|
||||
return
|
||||
@ -124,7 +136,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
[enabled, sendToFeed],
|
||||
)
|
||||
|
||||
const sendInteraction = React.useCallback(
|
||||
const sendInteraction = useCallback(
|
||||
(interaction: AppBskyFeedDefs.Interaction) => {
|
||||
if (!enabled) {
|
||||
return
|
||||
@ -138,7 +150,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
[enabled, sendToFeed],
|
||||
)
|
||||
|
||||
return React.useMemo(() => {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
enabled,
|
||||
// pass this method to the <List> onItemSeen
|
||||
@ -146,14 +158,15 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) {
|
||||
// call on various events
|
||||
// queues the event to be sent with the throttled sendToFeed call
|
||||
sendInteraction,
|
||||
feedDescriptor: feed,
|
||||
}
|
||||
}, [enabled, onItemSeen, sendInteraction])
|
||||
}, [enabled, onItemSeen, sendInteraction, feed])
|
||||
}
|
||||
|
||||
export const FeedFeedbackProvider = stateContext.Provider
|
||||
|
||||
export function useFeedFeedbackContext() {
|
||||
return React.useContext(stateContext)
|
||||
return useContext(stateContext)
|
||||
}
|
||||
|
||||
// TODO
|
||||
@ -161,8 +174,8 @@ export function useFeedFeedbackContext() {
|
||||
// take advantage of the feed feedback API. Until that's in
|
||||
// place, we're hardcoding it to the discover feed.
|
||||
// -prf
|
||||
function isDiscoverFeed(feed: FeedDescriptor) {
|
||||
return FEEDBACK_FEEDS.includes(feed)
|
||||
function isDiscoverFeed(feed?: FeedDescriptor) {
|
||||
return !!feed && FEEDBACK_FEEDS.includes(feed)
|
||||
}
|
||||
|
||||
function toString(interaction: AppBskyFeedDefs.Interaction): string {
|
||||
|
@ -46,6 +46,8 @@ type OtherNotificationType =
|
||||
| 'feedgen-like'
|
||||
| 'verified'
|
||||
| 'unverified'
|
||||
| 'like-via-repost'
|
||||
| 'repost-via-repost'
|
||||
| 'unknown'
|
||||
|
||||
type FeedNotificationBase = {
|
||||
|
@ -244,7 +244,9 @@ function toKnownType(
|
||||
notif.reason === 'follow' ||
|
||||
notif.reason === 'starterpack-joined' ||
|
||||
notif.reason === 'verified' ||
|
||||
notif.reason === 'unverified'
|
||||
notif.reason === 'unverified' ||
|
||||
notif.reason === 'like-via-repost' ||
|
||||
notif.reason === 'repost-via-repost'
|
||||
) {
|
||||
return notif.reason as NotificationType
|
||||
}
|
||||
@ -257,7 +259,12 @@ function getSubjectUri(
|
||||
): string | undefined {
|
||||
if (type === 'reply' || type === 'quote' || type === 'mention') {
|
||||
return notif.uri
|
||||
} else if (type === 'post-like' || type === 'repost') {
|
||||
} else if (
|
||||
type === 'post-like' ||
|
||||
type === 'repost' ||
|
||||
type === 'like-via-repost' ||
|
||||
type === 'repost-via-repost'
|
||||
) {
|
||||
if (
|
||||
bsky.dangerousIsType<AppBskyFeedRepost.Record>(
|
||||
notif.record,
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {useCallback} from 'react'
|
||||
import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig'
|
||||
import {logEvent, type LogEvents, toClout} from '#/lib/statsig/statsig'
|
||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
|
||||
@ -98,6 +98,7 @@ export function useGetPosts() {
|
||||
|
||||
export function usePostLikeMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
viaRepost: {uri: string; cid: string} | undefined,
|
||||
logContext: LogEvents['post:like']['logContext'] &
|
||||
LogEvents['post:unlike']['logContext'],
|
||||
) {
|
||||
@ -115,6 +116,7 @@ export function usePostLikeMutationQueue(
|
||||
const {uri: likeUri} = await likeMutation.mutateAsync({
|
||||
uri: postUri,
|
||||
cid: postCid,
|
||||
via: viaRepost,
|
||||
})
|
||||
userActionHistory.like([postUri])
|
||||
return likeUri
|
||||
@ -167,9 +169,9 @@ function usePostLikeMutation(
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the like
|
||||
Error,
|
||||
{uri: string; cid: string} // the post's uri and cid
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
mutationFn: ({uri, cid}) => {
|
||||
mutationFn: ({uri, cid, via}) => {
|
||||
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
|
||||
if (currentAccount) {
|
||||
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||
@ -190,7 +192,7 @@ function usePostLikeMutation(
|
||||
? toClout(post.likeCount + post.repostCount + post.replyCount)
|
||||
: undefined,
|
||||
})
|
||||
return agent.like(uri, cid)
|
||||
return agent.like(uri, cid, via)
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -209,6 +211,7 @@ function usePostUnlikeMutation(
|
||||
|
||||
export function usePostRepostMutationQueue(
|
||||
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||
viaRepost: {uri: string; cid: string} | undefined,
|
||||
logContext: LogEvents['post:repost']['logContext'] &
|
||||
LogEvents['post:unrepost']['logContext'],
|
||||
) {
|
||||
@ -226,6 +229,7 @@ export function usePostRepostMutationQueue(
|
||||
const {uri: repostUri} = await repostMutation.mutateAsync({
|
||||
uri: postUri,
|
||||
cid: postCid,
|
||||
via: viaRepost,
|
||||
})
|
||||
return repostUri
|
||||
} else {
|
||||
@ -272,11 +276,11 @@ function usePostRepostMutation(
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the repost
|
||||
Error,
|
||||
{uri: string; cid: string} // the post's uri and cid
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
mutationFn: post => {
|
||||
mutationFn: ({uri, cid, via}) => {
|
||||
logEvent('post:repost', {logContext})
|
||||
return agent.repost(post.uri, post.cid)
|
||||
return agent.repost(uri, cid, via)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
73
src/state/unstable-post-source.tsx
Normal file
73
src/state/unstable-post-source.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import {createContext, useCallback, useContext, useState} from 'react'
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
|
||||
import {type FeedDescriptor} from './queries/post-feed'
|
||||
|
||||
/**
|
||||
* For passing the source of the post (i.e. the original post, from the feed) to the threadview,
|
||||
* without using query params. Deliberately unstable to avoid using query params, use for FeedFeedback
|
||||
* and other ephemeral non-critical systems.
|
||||
*/
|
||||
|
||||
type Source = {
|
||||
post: AppBskyFeedDefs.FeedViewPost
|
||||
feed?: FeedDescriptor
|
||||
}
|
||||
|
||||
const SetUnstablePostSourceContext = createContext<
|
||||
(key: string, source: Source) => void
|
||||
>(() => {})
|
||||
const ConsumeUnstablePostSourceContext = createContext<
|
||||
(uri: string) => Source | undefined
|
||||
>(() => undefined)
|
||||
|
||||
export function Provider({children}: {children: React.ReactNode}) {
|
||||
const [sources, setSources] = useState<Map<string, Source>>(() => new Map())
|
||||
|
||||
const setUnstablePostSource = useCallback((key: string, source: Source) => {
|
||||
setSources(prev => {
|
||||
const newMap = new Map(prev)
|
||||
newMap.set(key, source)
|
||||
return newMap
|
||||
})
|
||||
}, [])
|
||||
|
||||
const consumeUnstablePostSource = useCallback(
|
||||
(uri: string) => {
|
||||
const source = sources.get(uri)
|
||||
if (source) {
|
||||
setSources(prev => {
|
||||
const newMap = new Map(prev)
|
||||
newMap.delete(uri)
|
||||
return newMap
|
||||
})
|
||||
}
|
||||
return source
|
||||
},
|
||||
[sources],
|
||||
)
|
||||
|
||||
return (
|
||||
<SetUnstablePostSourceContext.Provider value={setUnstablePostSource}>
|
||||
<ConsumeUnstablePostSourceContext.Provider
|
||||
value={consumeUnstablePostSource}>
|
||||
{children}
|
||||
</ConsumeUnstablePostSourceContext.Provider>
|
||||
</SetUnstablePostSourceContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSetUnstablePostSource() {
|
||||
return useContext(SetUnstablePostSourceContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* DANGER - This hook is unstable and should only be used for FeedFeedback
|
||||
* and other ephemeral non-critical systems. Does not change when the URI changes.
|
||||
*/
|
||||
export function useUnstablePostSource(uri: string) {
|
||||
const consume = useContext(ConsumeUnstablePostSourceContext)
|
||||
|
||||
const [source] = useState(() => consume(uri))
|
||||
return source
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
ListRenderItemInfo,
|
||||
type ListRenderItemInfo,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
@ -16,7 +16,7 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {List, ListRef} from '#/view/com/util/List'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||
import {NotificationFeedItem} from './NotificationFeedItem'
|
||||
|
@ -446,6 +446,55 @@ let NotificationFeedItem = ({
|
||||
</Trans>
|
||||
)
|
||||
icon = <VerifiedCheck size="xl" fill={t.palette.contrast_500} />
|
||||
} else if (item.type === 'like-via-repost') {
|
||||
a11yLabel = hasMultipleAuthors
|
||||
? _(
|
||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||
one: `${formattedAuthorsCount} other`,
|
||||
other: `${formattedAuthorsCount} others`,
|
||||
})} liked your repost`,
|
||||
)
|
||||
: _(msg`${firstAuthorName} liked your repost`)
|
||||
notificationContent = hasMultipleAuthors ? (
|
||||
<Trans>
|
||||
{firstAuthorLink} and{' '}
|
||||
<Text style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
<Plural
|
||||
value={additionalAuthorsCount}
|
||||
one={`${formattedAuthorsCount} other`}
|
||||
other={`${formattedAuthorsCount} others`}
|
||||
/>
|
||||
</Text>{' '}
|
||||
liked your repost
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>{firstAuthorLink} liked your repost</Trans>
|
||||
)
|
||||
} else if (item.type === 'repost-via-repost') {
|
||||
a11yLabel = hasMultipleAuthors
|
||||
? _(
|
||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||
one: `${formattedAuthorsCount} other`,
|
||||
other: `${formattedAuthorsCount} others`,
|
||||
})} reposted your repost`,
|
||||
)
|
||||
: _(msg`${firstAuthorName} reposted your repost`)
|
||||
notificationContent = hasMultipleAuthors ? (
|
||||
<Trans>
|
||||
{firstAuthorLink} and{' '}
|
||||
<Text style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
<Plural
|
||||
value={additionalAuthorsCount}
|
||||
one={`${formattedAuthorsCount} other`}
|
||||
other={`${formattedAuthorsCount} others`}
|
||||
/>
|
||||
</Text>{' '}
|
||||
reposted your repost
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>{firstAuthorLink} reposted your repost</Trans>
|
||||
)
|
||||
icon = <RepostIcon size="xl" style={{color: t.palette.positive_600}} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@ -553,7 +602,10 @@ let NotificationFeedItem = ({
|
||||
</TimeElapsed>
|
||||
</Text>
|
||||
</ExpandListPressable>
|
||||
{item.type === 'post-like' || item.type === 'repost' ? (
|
||||
{item.type === 'post-like' ||
|
||||
item.type === 'repost' ||
|
||||
item.type === 'like-via-repost' ||
|
||||
item.type === 'repost-via-repost' ? (
|
||||
<View style={[a.pt_2xs]}>
|
||||
<AdditionalPostText post={item.subject} />
|
||||
</View>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, {memo, useMemo} from 'react'
|
||||
import {memo, useCallback, useMemo, useState} from 'react'
|
||||
import {
|
||||
type GestureResponderEvent,
|
||||
StyleSheet,
|
||||
@ -6,7 +6,7 @@ import {
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
type AppBskyFeedThreadgate,
|
||||
AtUri,
|
||||
@ -35,10 +35,12 @@ import {
|
||||
usePostShadow,
|
||||
} from '#/state/cache/post-shadow'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {type ThreadPost} from '#/state/queries/post-thread'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {useUnstablePostSource} from '#/state/unstable-post-source'
|
||||
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {Link, TextLink} from '#/view/com/util/Link'
|
||||
@ -201,18 +203,21 @@ let PostThreadItemLoaded = ({
|
||||
hideTopBorder?: boolean
|
||||
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||
}): React.ReactNode => {
|
||||
const {currentAccount, hasSession} = useSession()
|
||||
const source = useUnstablePostSource(post.uri)
|
||||
const feedFeedback = useFeedFeedback(source?.feed, hasSession)
|
||||
|
||||
const t = useTheme()
|
||||
const pal = usePalette('default')
|
||||
const {_, i18n} = useLingui()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const [limitLines, setLimitLines] = React.useState(
|
||||
const [limitLines, setLimitLines] = useState(
|
||||
() => countLines(richText?.text) >= MAX_POST_LINES,
|
||||
)
|
||||
const {currentAccount} = useSession()
|
||||
const shadowedPostAuthor = useProfileShadow(post.author)
|
||||
const rootUri = record.reply?.root?.uri || post.uri
|
||||
const postHref = React.useMemo(() => {
|
||||
const postHref = useMemo(() => {
|
||||
const urip = new AtUri(post.uri)
|
||||
return makeProfileLink(post.author, 'post', urip.rkey)
|
||||
}, [post.uri, post.author])
|
||||
@ -220,12 +225,12 @@ let PostThreadItemLoaded = ({
|
||||
const authorHref = makeProfileLink(post.author)
|
||||
const authorTitle = post.author.handle
|
||||
const isThreadAuthor = getThreadAuthor(post, record) === currentAccount?.did
|
||||
const likesHref = React.useMemo(() => {
|
||||
const likesHref = useMemo(() => {
|
||||
const urip = new AtUri(post.uri)
|
||||
return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by')
|
||||
}, [post.uri, post.author])
|
||||
const likesTitle = _(msg`Likes on this post`)
|
||||
const repostsHref = React.useMemo(() => {
|
||||
const repostsHref = useMemo(() => {
|
||||
const urip = new AtUri(post.uri)
|
||||
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
|
||||
}, [post.uri, post.author])
|
||||
@ -233,7 +238,7 @@ let PostThreadItemLoaded = ({
|
||||
const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({
|
||||
threadgateRecord,
|
||||
})
|
||||
const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => {
|
||||
const additionalPostAlerts: AppModerationCause[] = useMemo(() => {
|
||||
const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri)
|
||||
const isControlledByViewer = new AtUri(rootUri).host === currentAccount?.did
|
||||
return isControlledByViewer && isPostHiddenByThreadgate
|
||||
@ -246,7 +251,7 @@ let PostThreadItemLoaded = ({
|
||||
]
|
||||
: []
|
||||
}, [post, currentAccount?.did, threadgateHiddenReplies, rootUri])
|
||||
const quotesHref = React.useMemo(() => {
|
||||
const quotesHref = useMemo(() => {
|
||||
const urip = new AtUri(post.uri)
|
||||
return makeProfileLink(post.author, 'post', urip.rkey, 'quotes')
|
||||
}, [post.uri, post.author])
|
||||
@ -270,7 +275,15 @@ let PostThreadItemLoaded = ({
|
||||
[post, langPrefs.primaryLanguage],
|
||||
)
|
||||
|
||||
const onPressReply = React.useCallback(() => {
|
||||
const onPressReply = () => {
|
||||
if (source) {
|
||||
feedFeedback.sendInteraction({
|
||||
item: post.uri,
|
||||
event: 'app.bsky.feed.defs#interactionReply',
|
||||
feedContext: source.post.feedContext,
|
||||
reqId: source.post.reqId,
|
||||
})
|
||||
}
|
||||
openComposer({
|
||||
replyTo: {
|
||||
uri: post.uri,
|
||||
@ -282,14 +295,46 @@ let PostThreadItemLoaded = ({
|
||||
},
|
||||
onPost: onPostReply,
|
||||
})
|
||||
}, [openComposer, post, record, onPostReply, moderation])
|
||||
}
|
||||
|
||||
const onPressShowMore = React.useCallback(() => {
|
||||
const onOpenAuthor = () => {
|
||||
if (source) {
|
||||
feedFeedback.sendInteraction({
|
||||
item: post.uri,
|
||||
event: 'app.bsky.feed.defs#clickthroughAuthor',
|
||||
feedContext: source.post.feedContext,
|
||||
reqId: source.post.reqId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onOpenEmbed = () => {
|
||||
if (source) {
|
||||
feedFeedback.sendInteraction({
|
||||
item: post.uri,
|
||||
event: 'app.bsky.feed.defs#clickthroughEmbed',
|
||||
feedContext: source.post.feedContext,
|
||||
reqId: source.post.reqId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onPressShowMore = useCallback(() => {
|
||||
setLimitLines(false)
|
||||
}, [setLimitLines])
|
||||
|
||||
const {isActive: live} = useActorStatus(post.author)
|
||||
|
||||
const reason = source?.post.reason
|
||||
const viaRepost = useMemo(() => {
|
||||
if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) {
|
||||
return {
|
||||
uri: reason.uri,
|
||||
cid: reason.cid,
|
||||
}
|
||||
}
|
||||
}, [reason])
|
||||
|
||||
if (!record) {
|
||||
return <ErrorMessage message={_(msg`Invalid or unsupported post record`)} />
|
||||
}
|
||||
@ -309,10 +354,8 @@ let PostThreadItemLoaded = ({
|
||||
<View
|
||||
style={[
|
||||
styles.replyLine,
|
||||
{
|
||||
flexGrow: 1,
|
||||
backgroundColor: pal.colors.replyLine,
|
||||
},
|
||||
a.flex_grow,
|
||||
{backgroundColor: pal.colors.replyLine},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
@ -334,13 +377,15 @@ let PostThreadItemLoaded = ({
|
||||
moderation={moderation.ui('avatar')}
|
||||
type={post.author.associated?.labeler ? 'labeler' : 'user'}
|
||||
live={live}
|
||||
onBeforePress={onOpenAuthor}
|
||||
/>
|
||||
<View style={[a.flex_1]}>
|
||||
<View style={[a.flex_row, a.align_center]}>
|
||||
<Link
|
||||
style={[a.flex_shrink]}
|
||||
href={authorHref}
|
||||
title={authorTitle}>
|
||||
title={authorTitle}
|
||||
onBeforePress={onOpenAuthor}>
|
||||
<Text
|
||||
emoji
|
||||
style={[
|
||||
@ -413,6 +458,7 @@ let PostThreadItemLoaded = ({
|
||||
embed={post.embed}
|
||||
moderation={moderation}
|
||||
viewContext={PostEmbedViewContext.ThreadHighlighted}
|
||||
onOpen={onOpenEmbed}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
@ -494,16 +540,21 @@ let PostThreadItemLoaded = ({
|
||||
marginLeft: -5,
|
||||
},
|
||||
]}>
|
||||
<PostControls
|
||||
big
|
||||
post={post}
|
||||
record={record}
|
||||
richText={richText}
|
||||
onPressReply={onPressReply}
|
||||
onPostReply={onPostReply}
|
||||
logContext="PostThreadItem"
|
||||
threadgateRecord={threadgateRecord}
|
||||
/>
|
||||
<FeedFeedbackProvider value={feedFeedback}>
|
||||
<PostControls
|
||||
big
|
||||
post={post}
|
||||
record={record}
|
||||
richText={richText}
|
||||
onPressReply={onPressReply}
|
||||
onPostReply={onPostReply}
|
||||
logContext="PostThreadItem"
|
||||
threadgateRecord={threadgateRecord}
|
||||
feedContext={source?.post?.feedContext}
|
||||
reqId={source?.post?.reqId}
|
||||
viaRepost={viaRepost}
|
||||
/>
|
||||
</FeedFeedbackProvider>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@ -779,7 +830,7 @@ function ExpandedPostDetails({
|
||||
const isRootPost = !('reply' in post.record)
|
||||
const langPrefs = useLanguagePrefs()
|
||||
|
||||
const onTranslatePress = React.useCallback(
|
||||
const onTranslatePress = useCallback(
|
||||
(e: GestureResponderEvent) => {
|
||||
e.preventDefault()
|
||||
openLink(translatorUrl, true)
|
||||
|
@ -33,9 +33,10 @@ import {
|
||||
usePostShadow,
|
||||
} from '#/state/cache/post-shadow'
|
||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||
import {precacheProfile} from '#/state/queries/profile'
|
||||
import {unstableCacheProfileView} from '#/state/queries/profile'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {useSetUnstablePostSource} from '#/state/unstable-post-source'
|
||||
import {FeedNameText} from '#/view/com/util/FeedInfoText'
|
||||
import {Link, TextLink, TextLinkOnWebOnly} from '#/view/com/util/Link'
|
||||
import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
|
||||
@ -174,7 +175,8 @@ let FeedItemInner = ({
|
||||
const urip = new AtUri(post.uri)
|
||||
return makeProfileLink(post.author, 'post', urip.rkey)
|
||||
}, [post.uri, post.author])
|
||||
const {sendInteraction} = useFeedFeedbackContext()
|
||||
const {sendInteraction, feedDescriptor} = useFeedFeedbackContext()
|
||||
const unstableSetPostSource = useSetUnstablePostSource()
|
||||
|
||||
const onPressReply = () => {
|
||||
sendInteraction({
|
||||
@ -229,7 +231,16 @@ let FeedItemInner = ({
|
||||
feedContext,
|
||||
reqId,
|
||||
})
|
||||
precacheProfile(queryClient, post.author)
|
||||
unstableCacheProfileView(queryClient, post.author)
|
||||
unstableSetPostSource(post.uri, {
|
||||
feed: feedDescriptor,
|
||||
post: {
|
||||
post,
|
||||
reason: AppBskyFeedDefs.isReasonRepost(reason) ? reason : undefined,
|
||||
feedContext,
|
||||
reqId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const outerStyles = [
|
||||
@ -263,6 +274,15 @@ let FeedItemInner = ({
|
||||
|
||||
const {isActive: live} = useActorStatus(post.author)
|
||||
|
||||
const viaRepost = useMemo(() => {
|
||||
if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) {
|
||||
return {
|
||||
uri: reason.uri,
|
||||
cid: reason.cid,
|
||||
}
|
||||
}
|
||||
}, [reason])
|
||||
|
||||
return (
|
||||
<Link
|
||||
testID={`feedItem-by-${post.author.handle}`}
|
||||
@ -450,6 +470,7 @@ let FeedItemInner = ({
|
||||
reqId={reqId}
|
||||
threadgateRecord={threadgateRecord}
|
||||
onShowLess={onShowLess}
|
||||
viaRepost={viaRepost}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
88
yarn.lock
88
yarn.lock
@ -63,10 +63,10 @@
|
||||
"@atproto/xrpc" "^0.7.0"
|
||||
"@atproto/xrpc-server" "^0.7.18"
|
||||
|
||||
"@atproto/api@^0.15.8":
|
||||
version "0.15.8"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.15.8.tgz#f284a9c225191ebd35b46f5695932ab649c04a61"
|
||||
integrity sha512-PsCgmV4zPjN8VuJMruxqauhn88PuS0b8t2Xsjl4617+bCPpY513jVlxgNH/XExxO7TSVvJM7EzdLY4o3fqh/xQ==
|
||||
"@atproto/api@^0.15.9":
|
||||
version "0.15.9"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.15.9.tgz#f8c40afd6e414ab107d63d6f08d9e264bf9a149a"
|
||||
integrity sha512-CyAILiIcbN+V5CFAI6MDb247epm25RGkP7HSan5LUaOHiyg1NCAmflWCN/bbMdJX9kLqjAPAG3eN4BUUbYe//Q==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.4.2"
|
||||
"@atproto/lexicon" "^0.4.11"
|
||||
@ -94,14 +94,14 @@
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/bsky@^0.0.150":
|
||||
version "0.0.150"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.150.tgz#6626095875d805d0d3f38fa4e184b9f7d274c80f"
|
||||
integrity sha512-dn1jzP1EId842+g78Q6EMdOmgEZxa9bSq20HMdd5/R8uu559mPs8zigFuddqCoT1fRaJXFC8ZP7Jk5asvBQhrA==
|
||||
"@atproto/bsky@^0.0.151":
|
||||
version "0.0.151"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.151.tgz#a0e5b59e163a3b74379fb547601be4fc66b7a133"
|
||||
integrity sha512-42pvUsyGw0nR6Sxlda824maY4gBxUni1cXPG+7uGe6Ixm6XAaPhfTgT1rAg++1rDXH9tT1EXAVnMxg38S6osLg==
|
||||
dependencies:
|
||||
"@atproto-labs/fetch-node" "0.1.9"
|
||||
"@atproto-labs/xrpc-utils" "0.0.14"
|
||||
"@atproto/api" "^0.15.8"
|
||||
"@atproto/api" "^0.15.9"
|
||||
"@atproto/common" "^0.4.11"
|
||||
"@atproto/crypto" "^0.4.4"
|
||||
"@atproto/did" "^0.1.5"
|
||||
@ -218,20 +218,20 @@
|
||||
"@noble/hashes" "^1.6.1"
|
||||
uint8arrays "3.0.0"
|
||||
|
||||
"@atproto/dev-env@^0.3.132":
|
||||
version "0.3.132"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.132.tgz#78d55ef08a368a752c55b1ee7b7c08a41f27b5ac"
|
||||
integrity sha512-RFd/9kgvmbP859N6NLu/FxCzLsj01iq22P9jNpL+dQNXbWXHYwGMUa6edf/ZrljNi3dFBNxabdDZJ2q+8uvBJQ==
|
||||
"@atproto/dev-env@^0.3.133":
|
||||
version "0.3.133"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.133.tgz#4ca58c9c4c99f001f26ce50629214f81d6acd3ab"
|
||||
integrity sha512-GtKDa+q0Fx2tJZL44cDAINMCxNmt1aKkGVpW/6PTnuSSjdA7ErBUEL3opbwgaAcPRGZfscB0mQmGfWR0BUmvUw==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.15.8"
|
||||
"@atproto/bsky" "^0.0.150"
|
||||
"@atproto/api" "^0.15.9"
|
||||
"@atproto/bsky" "^0.0.151"
|
||||
"@atproto/bsync" "^0.0.19"
|
||||
"@atproto/common-web" "^0.4.2"
|
||||
"@atproto/crypto" "^0.4.4"
|
||||
"@atproto/identity" "^0.4.8"
|
||||
"@atproto/lexicon" "^0.4.11"
|
||||
"@atproto/ozone" "^0.1.111"
|
||||
"@atproto/pds" "^0.4.138"
|
||||
"@atproto/ozone" "^0.1.112"
|
||||
"@atproto/pds" "^0.4.139"
|
||||
"@atproto/sync" "^0.1.23"
|
||||
"@atproto/syntax" "^0.4.0"
|
||||
"@atproto/xrpc-server" "^0.7.18"
|
||||
@ -294,24 +294,24 @@
|
||||
"@atproto/jwk" "0.1.5"
|
||||
"@atproto/oauth-types" "0.2.7"
|
||||
|
||||
"@atproto/oauth-provider-frontend@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.1.4.tgz#240a2e58c29d32fa7d4ea9d142c00c23d2469452"
|
||||
integrity sha512-TLKL5lTmSieHx7+3RVIx7rIxRPP1SNCwzzdTvYB46yd1XrGHdPU//M6CP5OZ1BvcxF6H4JXIkOSWvFseol+gOw==
|
||||
optionalDependencies:
|
||||
"@atproto/oauth-provider-api" "0.1.2"
|
||||
|
||||
"@atproto/oauth-provider-ui@0.1.5":
|
||||
"@atproto/oauth-provider-frontend@0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.1.5.tgz#b080c5e814975821689c5976c27ac1081211106f"
|
||||
integrity sha512-pW0Vx3kvIWH1Mu3SOImNHP9JbmhSj2e3ChDvtfXCWI1oC03fiaMlJfdxrx9Plq5Z+DajnCzPzrf1Lvbopjf94Q==
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.1.5.tgz#66fd8760fade2ac94111ad5389f33f4d8ce5bba2"
|
||||
integrity sha512-FdDBuwy827+etjIcRwZU7dtxa8Ltso3ufVLMEi8A2V91v21XDysZjLANC6cvmNNSUcS4E/J6ZAwTrQDo7O5axw==
|
||||
optionalDependencies:
|
||||
"@atproto/oauth-provider-api" "0.1.2"
|
||||
|
||||
"@atproto/oauth-provider@^0.7.7":
|
||||
version "0.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.7.7.tgz#dbbdeb405ab1d239fd926340f83fb41e13455011"
|
||||
integrity sha512-ElphzmOjw1hr42HN4dD6sMAQFtpTkaJ8bBDAsbL9YBVJDEGhmHsF3Ye8mDUO4nhEdg7PUTWiCzXyqnaorAjiTA==
|
||||
"@atproto/oauth-provider-ui@0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.1.6.tgz#4bae995ff57671ac3915f58fdb2cf6a76a0fe42d"
|
||||
integrity sha512-pJzV9ouNj1/TDUCl3CWEZrHoUese4lcKx5F59t2OiLFm2K7T7QrszKUIMyU5QdiQHv551B0ZJOkJ8+4b/fVGPA==
|
||||
optionalDependencies:
|
||||
"@atproto/oauth-provider-api" "0.1.2"
|
||||
|
||||
"@atproto/oauth-provider@^0.7.8":
|
||||
version "0.7.8"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.7.8.tgz#287b15eb6b0bc0bb4b2da2339150253db006c6e0"
|
||||
integrity sha512-+dEU9dTyfWKeZ/Nu7ocR6fO73RcG0vwDjT45vgcnM9L7jtuPk9zfpmiR4ODYBk9QUu2DURo9yBhtXNJI3Yz8aQ==
|
||||
dependencies:
|
||||
"@atproto-labs/fetch" "0.2.3"
|
||||
"@atproto-labs/fetch-node" "0.1.9"
|
||||
@ -322,8 +322,8 @@
|
||||
"@atproto/jwk" "0.1.5"
|
||||
"@atproto/jwk-jose" "0.1.6"
|
||||
"@atproto/oauth-provider-api" "0.1.2"
|
||||
"@atproto/oauth-provider-frontend" "0.1.4"
|
||||
"@atproto/oauth-provider-ui" "0.1.5"
|
||||
"@atproto/oauth-provider-frontend" "0.1.5"
|
||||
"@atproto/oauth-provider-ui" "0.1.6"
|
||||
"@atproto/oauth-types" "0.2.7"
|
||||
"@atproto/syntax" "0.4.0"
|
||||
"@hapi/accept" "^6.0.3"
|
||||
@ -346,12 +346,12 @@
|
||||
"@atproto/jwk" "0.1.5"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/ozone@^0.1.111":
|
||||
version "0.1.111"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.111.tgz#7ef4a02f1af045ab44254fb9d44ab0e50fd94ba9"
|
||||
integrity sha512-NY+Cn/3dY4tPFkMUoJR1KMZN/v9ZIxjx6EQBMwn/nqTiHk0E3rtGEbyL2jLQ7x+FxpPTjDgpnn3K725+8XUaAg==
|
||||
"@atproto/ozone@^0.1.112":
|
||||
version "0.1.112"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.112.tgz#6b6b5ac052dd4e6dfec3c88f83c9b53f4902fcbe"
|
||||
integrity sha512-Euut64N/4UyRXyV6m1ATE9K6o6EpCf46ozD4GG8HJ9AC5zEgBYMSkH4l6SLrhKrYYIGXkvglk1WYuuDQKYb3LA==
|
||||
dependencies:
|
||||
"@atproto/api" "^0.15.8"
|
||||
"@atproto/api" "^0.15.9"
|
||||
"@atproto/common" "^0.4.11"
|
||||
"@atproto/crypto" "^0.4.4"
|
||||
"@atproto/identity" "^0.4.8"
|
||||
@ -376,20 +376,20 @@
|
||||
undici "^6.14.1"
|
||||
ws "^8.12.0"
|
||||
|
||||
"@atproto/pds@^0.4.138":
|
||||
version "0.4.138"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.138.tgz#437d785c83f710bf37bef8baf687b0a46ce9dc68"
|
||||
integrity sha512-WLzDhmguTgs2wQNKoGxCbpKNegDnRiemSslenMbPrB7kSiXYj+XZobLyoIXHv1EnAd2pbThwNEL8z8EfkM0mDg==
|
||||
"@atproto/pds@^0.4.139":
|
||||
version "0.4.139"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.139.tgz#70ae5afd7d90eab214c652d57a5e6478af454fbe"
|
||||
integrity sha512-VD1VTSAnbAme4D4Xk/Wdl05qs8YbCe39/i960EyXzw2fYNvL9jMpKm3z0lwhrYN9q7phFhr2ubU2QjfRFDbDAQ==
|
||||
dependencies:
|
||||
"@atproto-labs/fetch-node" "0.1.9"
|
||||
"@atproto-labs/xrpc-utils" "0.0.14"
|
||||
"@atproto/api" "^0.15.8"
|
||||
"@atproto/api" "^0.15.9"
|
||||
"@atproto/aws" "^0.2.21"
|
||||
"@atproto/common" "^0.4.11"
|
||||
"@atproto/crypto" "^0.4.4"
|
||||
"@atproto/identity" "^0.4.8"
|
||||
"@atproto/lexicon" "^0.4.11"
|
||||
"@atproto/oauth-provider" "^0.7.7"
|
||||
"@atproto/oauth-provider" "^0.7.8"
|
||||
"@atproto/repo" "^0.8.1"
|
||||
"@atproto/syntax" "^0.4.0"
|
||||
"@atproto/xrpc" "^0.7.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user