Pinned posts (#5055)
* add to dropdown menu * use normal profile mutation hook * add pin as reason * request pins * shadow update * rm logs * get prev pinned from getProfile * fix toasts * invalidate after appview ready * don't mutate params * rm log * use checkCommited rather than manual whenAppViewReady * move to mutation * even more optimistic optimistic update * allow pins in `posts_and_author_threads` * update @atproto/api * add reasonPin type * fix strange type error in unrelated query * another missing type
This commit is contained in:
+1
-1
@@ -53,7 +53,7 @@
|
|||||||
"icons:optimize": "svgo -f ./assets/icons"
|
"icons:optimize": "svgo -f ./assets/icons"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.13.7",
|
"@atproto/api": "^0.13.8",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {FeedAPI, FeedAPIResponse} from './types'
|
|||||||
|
|
||||||
export class AuthorFeedAPI implements FeedAPI {
|
export class AuthorFeedAPI implements FeedAPI {
|
||||||
agent: BskyAgent
|
agent: BskyAgent
|
||||||
params: GetAuthorFeed.QueryParams
|
_params: GetAuthorFeed.QueryParams
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
agent,
|
agent,
|
||||||
@@ -18,7 +18,13 @@ export class AuthorFeedAPI implements FeedAPI {
|
|||||||
feedParams: GetAuthorFeed.QueryParams
|
feedParams: GetAuthorFeed.QueryParams
|
||||||
}) {
|
}) {
|
||||||
this.agent = agent
|
this.agent = agent
|
||||||
this.params = feedParams
|
this._params = feedParams
|
||||||
|
}
|
||||||
|
|
||||||
|
get params() {
|
||||||
|
const params = {...this._params}
|
||||||
|
params.includePins = params.filter !== 'posts_with_media'
|
||||||
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||||
@@ -57,8 +63,9 @@ export class AuthorFeedAPI implements FeedAPI {
|
|||||||
return feed.filter(post => {
|
return feed.filter(post => {
|
||||||
const isReply = post.reply
|
const isReply = post.reply
|
||||||
const isRepost = AppBskyFeedDefs.isReasonRepost(post.reason)
|
const isRepost = AppBskyFeedDefs.isReasonRepost(post.reason)
|
||||||
|
const isPin = AppBskyFeedDefs.isReasonPin(post.reason)
|
||||||
if (!isReply) return true
|
if (!isReply) return true
|
||||||
if (isRepost) return true
|
if (isRepost || isPin) return true
|
||||||
return isReply && isAuthorReplyChain(this.params.actor, post, feed)
|
return isReply && isAuthorReplyChain(this.params.actor, post, feed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -21,6 +21,7 @@ export interface PostShadow {
|
|||||||
repostUri: string | undefined
|
repostUri: string | undefined
|
||||||
isDeleted: boolean
|
isDeleted: boolean
|
||||||
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
||||||
|
pinned: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const POST_TOMBSTONE = Symbol('PostTombstone')
|
export const POST_TOMBSTONE = Symbol('PostTombstone')
|
||||||
@@ -113,6 +114,7 @@ function mergeShadow(
|
|||||||
...(post.viewer || {}),
|
...(post.viewer || {}),
|
||||||
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
||||||
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
||||||
|
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
import {updatePostShadow} from '../cache/post-shadow'
|
||||||
|
import {useAgent, useSession} from '../session'
|
||||||
|
import {useProfileUpdateMutation} from './profile'
|
||||||
|
|
||||||
|
export function usePinnedPostMutation() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const agent = useAgent()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const {mutateAsync: profileUpdateMutate} = useProfileUpdateMutation()
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
postUri,
|
||||||
|
postCid,
|
||||||
|
action,
|
||||||
|
}: {
|
||||||
|
postUri: string
|
||||||
|
postCid: string
|
||||||
|
action: 'pin' | 'unpin'
|
||||||
|
}) => {
|
||||||
|
const pinCurrentPost = action === 'pin'
|
||||||
|
let prevPinnedPost: string | undefined
|
||||||
|
try {
|
||||||
|
updatePostShadow(queryClient, postUri, {pinned: pinCurrentPost})
|
||||||
|
|
||||||
|
// get the currently pinned post so we can optimistically remove the pin from it
|
||||||
|
if (!currentAccount) throw new Error('Not logged in')
|
||||||
|
const {data: profile} = await agent.getProfile({
|
||||||
|
actor: currentAccount.did,
|
||||||
|
})
|
||||||
|
prevPinnedPost = profile.pinnedPost?.uri
|
||||||
|
if (prevPinnedPost && prevPinnedPost !== postUri) {
|
||||||
|
updatePostShadow(queryClient, prevPinnedPost, {pinned: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
await profileUpdateMutate({
|
||||||
|
profile,
|
||||||
|
updates: existing => {
|
||||||
|
existing.pinnedPost = pinCurrentPost
|
||||||
|
? {uri: postUri, cid: postCid}
|
||||||
|
: undefined
|
||||||
|
return existing
|
||||||
|
},
|
||||||
|
checkCommitted: res =>
|
||||||
|
pinCurrentPost
|
||||||
|
? res.data.pinnedPost?.uri === postUri
|
||||||
|
: !res.data.pinnedPost,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (pinCurrentPost) {
|
||||||
|
Toast.show(_(msg`Post pinned`))
|
||||||
|
} else {
|
||||||
|
Toast.show(_(msg`Post unpinned`))
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: FEED_RQKEY(
|
||||||
|
`author|${currentAccount.did}|posts_and_author_threads`,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: FEED_RQKEY(
|
||||||
|
`author|${currentAccount.did}|posts_with_replies`,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
Toast.show(_(msg`Failed to pin post`))
|
||||||
|
logger.error('Failed to pin post', {message: String(e)})
|
||||||
|
// revert optimistic update
|
||||||
|
updatePostShadow(queryClient, postUri, {
|
||||||
|
pinned: !pinCurrentPost,
|
||||||
|
})
|
||||||
|
if (prevPinnedPost && prevPinnedPost !== postUri) {
|
||||||
|
updatePostShadow(queryClient, prevPinnedPost, {pinned: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -91,6 +91,7 @@ export interface FeedPostSlice {
|
|||||||
feedContext: string | undefined
|
feedContext: string | undefined
|
||||||
reason?:
|
reason?:
|
||||||
| AppBskyFeedDefs.ReasonRepost
|
| AppBskyFeedDefs.ReasonRepost
|
||||||
|
| AppBskyFeedDefs.ReasonPin
|
||||||
| ReasonFeedSource
|
| ReasonFeedSource
|
||||||
| {[k: string]: unknown; $type: string}
|
| {[k: string]: unknown; $type: string}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ export function useProfileUpdateMutation() {
|
|||||||
} else {
|
} else {
|
||||||
existing.displayName = updates.displayName
|
existing.displayName = updates.displayName
|
||||||
existing.description = updates.description
|
existing.description = updates.description
|
||||||
|
if ('pinnedPost' in updates) {
|
||||||
|
existing.pinnedPost = updates.pinnedPost
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (newUserAvatarPromise) {
|
if (newUserAvatarPromise) {
|
||||||
const res = await newUserAvatarPromise
|
const res = await newUserAvatarPromise
|
||||||
|
|||||||
@@ -105,17 +105,16 @@ export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) {
|
|||||||
|
|
||||||
export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
|
export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
return useQuery<AppBskyGraphGetSuggestedFollowsByActor.OutputSchema, Error>({
|
return useQuery({
|
||||||
queryKey: suggestedFollowsByActorQueryKey(did),
|
queryKey: suggestedFollowsByActorQueryKey(did),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
|
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
|
||||||
actor: did,
|
actor: did,
|
||||||
})
|
})
|
||||||
const data = res.data.isFallback ? {suggestions: []} : res.data
|
const suggestions = res.data.isFallback
|
||||||
data.suggestions = data.suggestions.filter(profile => {
|
? []
|
||||||
return !profile.viewer?.following
|
: res.data.suggestions.filter(profile => !profile.viewer?.following)
|
||||||
})
|
return {suggestions}
|
||||||
return data
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ import {PostMeta} from '#/view/com/util/PostMeta'
|
|||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
|
import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
|
||||||
|
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
@@ -52,6 +53,7 @@ interface FeedItemProps {
|
|||||||
record: AppBskyFeedPost.Record
|
record: AppBskyFeedPost.Record
|
||||||
reason:
|
reason:
|
||||||
| AppBskyFeedDefs.ReasonRepost
|
| AppBskyFeedDefs.ReasonRepost
|
||||||
|
| AppBskyFeedDefs.ReasonPin
|
||||||
| ReasonFeedSource
|
| ReasonFeedSource
|
||||||
| {[k: string]: unknown; $type: string}
|
| {[k: string]: unknown; $type: string}
|
||||||
| undefined
|
| undefined
|
||||||
@@ -295,7 +297,7 @@ let FeedItemInner = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onBeforePress={onOpenReposter}>
|
onBeforePress={onOpenReposter}>
|
||||||
<Repost
|
<RepostIcon
|
||||||
style={{color: pal.colors.textLight, marginRight: 3}}
|
style={{color: pal.colors.textLight, marginRight: 3}}
|
||||||
width={14}
|
width={14}
|
||||||
height={14}
|
height={14}
|
||||||
@@ -337,6 +339,21 @@ let FeedItemInner = ({
|
|||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
|
) : AppBskyFeedDefs.isReasonPin(reason) ? (
|
||||||
|
<View style={styles.includeReason}>
|
||||||
|
<PinIcon
|
||||||
|
style={{color: pal.colors.textLight, marginRight: 3}}
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
type="sm-bold"
|
||||||
|
style={pal.textLight}
|
||||||
|
lineHeight={1.2}
|
||||||
|
numberOfLines={1}>
|
||||||
|
<Trans>Pinned</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {memo} from 'react'
|
import React, {memo, useCallback} from 'react'
|
||||||
import {
|
import {
|
||||||
Platform,
|
Platform,
|
||||||
Pressable,
|
Pressable,
|
||||||
@@ -18,9 +18,13 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {getCurrentRoute} from '#/lib/routes/helpers'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||||
|
import {shareUrl} from '#/lib/sharing'
|
||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||||
|
import {toShareUrl} from '#/lib/strings/url-helpers'
|
||||||
|
import {useTheme} from '#/lib/ThemeContext'
|
||||||
import {getTranslatorLink} from '#/locale/helpers'
|
import {getTranslatorLink} from '#/locale/helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
@@ -29,6 +33,7 @@ import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
|||||||
import {useLanguagePrefs} from '#/state/preferences'
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences'
|
import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences'
|
||||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||||
|
import {usePinnedPostMutation} from '#/state/queries/pinned-post'
|
||||||
import {
|
import {
|
||||||
usePostDeleteMutation,
|
usePostDeleteMutation,
|
||||||
useThreadMuteMutationQueue,
|
useThreadMuteMutationQueue,
|
||||||
@@ -38,10 +43,6 @@ import {getMaybeDetachedQuoteEmbed} from '#/state/queries/postgate/util'
|
|||||||
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
|
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
|
||||||
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 {getCurrentRoute} from 'lib/routes/helpers'
|
|
||||||
import {shareUrl} from 'lib/sharing'
|
|
||||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {atoms as a, useBreakpoints, useTheme as useAlf} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme as useAlf} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
@@ -65,6 +66,7 @@ import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/E
|
|||||||
import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
|
import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
|
||||||
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
|
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
|
||||||
import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
|
import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
|
||||||
|
import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
|
||||||
import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
|
import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
|
||||||
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
|
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
|
||||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||||
@@ -106,7 +108,9 @@ let PostDropdownBtn = ({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const defaultCtrlColor = theme.palette.default.postCtrl
|
const defaultCtrlColor = theme.palette.default.postCtrl
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
const postDeleteMutation = usePostDeleteMutation()
|
const {mutateAsync: deletePostMutate} = usePostDeleteMutation()
|
||||||
|
const {mutateAsync: pinPostMutate, isPending: isPinPending} =
|
||||||
|
usePinnedPostMutation()
|
||||||
const hiddenPosts = useHiddenPosts()
|
const hiddenPosts = useHiddenPosts()
|
||||||
const {hidePost} = useHiddenPostsApi()
|
const {hidePost} = useHiddenPostsApi()
|
||||||
const feedFeedback = useFeedFeedbackContext()
|
const feedFeedback = useFeedFeedbackContext()
|
||||||
@@ -149,8 +153,9 @@ let PostDropdownBtn = ({
|
|||||||
threadgateRecord,
|
threadgateRecord,
|
||||||
})
|
})
|
||||||
const isReplyHiddenByThreadgate = threadgateHiddenReplies.has(postUri)
|
const isReplyHiddenByThreadgate = threadgateHiddenReplies.has(postUri)
|
||||||
|
const isPinned = post.viewer?.pinned
|
||||||
|
|
||||||
const {mutateAsync: toggleQuoteDetachment, isPending} =
|
const {mutateAsync: toggleQuoteDetachment, isPending: isDetachPending} =
|
||||||
useToggleQuoteDetachmentMutation()
|
useToggleQuoteDetachmentMutation()
|
||||||
|
|
||||||
const prefetchPostInteractionSettings = usePrefetchPostInteractionSettings({
|
const prefetchPostInteractionSettings = usePrefetchPostInteractionSettings({
|
||||||
@@ -169,7 +174,7 @@ let PostDropdownBtn = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const onDeletePost = React.useCallback(() => {
|
const onDeletePost = React.useCallback(() => {
|
||||||
postDeleteMutation.mutateAsync({uri: postUri}).then(
|
deletePostMutate({uri: postUri}).then(
|
||||||
() => {
|
() => {
|
||||||
Toast.show(_(msg`Post deleted`))
|
Toast.show(_(msg`Post deleted`))
|
||||||
|
|
||||||
@@ -197,7 +202,7 @@ let PostDropdownBtn = ({
|
|||||||
}, [
|
}, [
|
||||||
navigation,
|
navigation,
|
||||||
postUri,
|
postUri,
|
||||||
postDeleteMutation,
|
deletePostMutate,
|
||||||
postAuthor,
|
postAuthor,
|
||||||
currentAccount,
|
currentAccount,
|
||||||
isAuthor,
|
isAuthor,
|
||||||
@@ -344,6 +349,14 @@ let PostDropdownBtn = ({
|
|||||||
toggleReplyVisibility,
|
toggleReplyVisibility,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const onPressPin = useCallback(() => {
|
||||||
|
pinPostMutate({
|
||||||
|
postUri,
|
||||||
|
postCid,
|
||||||
|
action: isPinned ? 'unpin' : 'pin',
|
||||||
|
})
|
||||||
|
}, [isPinned, pinPostMutate, postCid, postUri])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventStopper onKeyDown={false}>
|
<EventStopper onKeyDown={false}>
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
@@ -372,6 +385,33 @@ let PostDropdownBtn = ({
|
|||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
|
|
||||||
<Menu.Outer>
|
<Menu.Outer>
|
||||||
|
{isAuthor && (
|
||||||
|
<>
|
||||||
|
<Menu.Group>
|
||||||
|
<Menu.Item
|
||||||
|
testID="pinPostBtn"
|
||||||
|
label={
|
||||||
|
isPinned
|
||||||
|
? _(msg`Unpin from profile`)
|
||||||
|
: _(msg`Pin to your profile`)
|
||||||
|
}
|
||||||
|
disabled={isPinPending}
|
||||||
|
onPress={onPressPin}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
{isPinned
|
||||||
|
? _(msg`Unpin from profile`)
|
||||||
|
: _(msg`Pin to your profile`)}
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon
|
||||||
|
icon={isPinPending ? Loader : PinIcon}
|
||||||
|
position="right"
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
<Menu.Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
{(!hideInPWI || hasSession) && (
|
{(!hideInPWI || hasSession) && (
|
||||||
<>
|
<>
|
||||||
@@ -536,7 +576,7 @@ let PostDropdownBtn = ({
|
|||||||
|
|
||||||
{canDetachQuote && (
|
{canDetachQuote && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
disabled={isPending}
|
disabled={isDetachPending}
|
||||||
testID="postDropdownHideBtn"
|
testID="postDropdownHideBtn"
|
||||||
label={
|
label={
|
||||||
quoteEmbed.isDetached
|
quoteEmbed.isDetached
|
||||||
@@ -555,7 +595,7 @@ let PostDropdownBtn = ({
|
|||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
<Menu.ItemIcon
|
<Menu.ItemIcon
|
||||||
icon={
|
icon={
|
||||||
isPending
|
isDetachPending
|
||||||
? Loader
|
? Loader
|
||||||
: quoteEmbed.isDetached
|
: quoteEmbed.isDetached
|
||||||
? Eye
|
? Eye
|
||||||
|
|||||||
@@ -85,15 +85,15 @@
|
|||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
tlds "^1.234.0"
|
tlds "^1.234.0"
|
||||||
|
|
||||||
"@atproto/api@^0.13.7":
|
"@atproto/api@^0.13.8":
|
||||||
version "0.13.7"
|
version "0.13.8"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.7.tgz#072eba2025d5251505f17b0b5d2de33749ea5ee4"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.8.tgz#44aa4992442812604bccf9eebe4d1db9ed64c179"
|
||||||
integrity sha512-41kSLmFWDbuPOenb52WRq1lnBkSZrL+X29tWcvEt6SZXK4xBoKAalw1MjF+oabhzff12iMtNaNvmmt2fu1L+cw==
|
integrity sha512-1RlvMg8iAT5k3F0U3549ct9+jXthlXtfFXIfTXLyXXFe9Exfvmr7ZJ1ra41vU1nXGsoouCoTxj7kdzC4MY8JZg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "^0.3.0"
|
"@atproto/common-web" "^0.3.1"
|
||||||
"@atproto/lexicon" "^0.4.1"
|
"@atproto/lexicon" "^0.4.2"
|
||||||
"@atproto/syntax" "^0.3.0"
|
"@atproto/syntax" "^0.3.0"
|
||||||
"@atproto/xrpc" "^0.6.2"
|
"@atproto/xrpc" "^0.6.3"
|
||||||
await-lock "^2.2.2"
|
await-lock "^2.2.2"
|
||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
tlds "^1.234.0"
|
tlds "^1.234.0"
|
||||||
@@ -179,6 +179,16 @@
|
|||||||
uint8arrays "3.0.0"
|
uint8arrays "3.0.0"
|
||||||
zod "^3.21.4"
|
zod "^3.21.4"
|
||||||
|
|
||||||
|
"@atproto/common-web@^0.3.1":
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.1.tgz#86f8efb10a4b9073839cee914c6c08a664917cc4"
|
||||||
|
integrity sha512-N7wiTnus5vAr+lT//0y8m/FaHHLJ9LpGuEwkwDAeV3LCiPif4m/FS8x/QOYrx1PdZQwKso95RAPzCGWQBH5j6Q==
|
||||||
|
dependencies:
|
||||||
|
graphemer "^1.4.0"
|
||||||
|
multiformats "^9.9.0"
|
||||||
|
uint8arrays "3.0.0"
|
||||||
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@atproto/common@0.1.0":
|
"@atproto/common@0.1.0":
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210"
|
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210"
|
||||||
@@ -292,6 +302,17 @@
|
|||||||
multiformats "^9.9.0"
|
multiformats "^9.9.0"
|
||||||
zod "^3.23.8"
|
zod "^3.23.8"
|
||||||
|
|
||||||
|
"@atproto/lexicon@^0.4.2":
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.2.tgz#fcc92cdb82ae248b034b172763d6dbadfb00a829"
|
||||||
|
integrity sha512-CXoOkhcdF3XVUnR2oNgCs2ljWfo/8zUjxL5RIhJW/UNLp/FSl+KpF8Jm5fbk8Y/XXVPGRAsv9OYfxyU/14N/pw==
|
||||||
|
dependencies:
|
||||||
|
"@atproto/common-web" "^0.3.1"
|
||||||
|
"@atproto/syntax" "^0.3.0"
|
||||||
|
iso-datestring-validator "^2.2.2"
|
||||||
|
multiformats "^9.9.0"
|
||||||
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@atproto/oauth-provider@^0.1.2":
|
"@atproto/oauth-provider@^0.1.2":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.1.2.tgz#a576a4c7795c7938a994e76192c19a2e73ffcddf"
|
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.1.2.tgz#a576a4c7795c7938a994e76192c19a2e73ffcddf"
|
||||||
@@ -444,12 +465,12 @@
|
|||||||
"@atproto/lexicon" "^0.4.1"
|
"@atproto/lexicon" "^0.4.1"
|
||||||
zod "^3.23.8"
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@atproto/xrpc@^0.6.2":
|
"@atproto/xrpc@^0.6.3":
|
||||||
version "0.6.2"
|
version "0.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.2.tgz#634228a7e533de01bda2214837d11574fdadad55"
|
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.3.tgz#5942fc24644ad182b913af526efaa06a43d89478"
|
||||||
integrity sha512-as/gb08xJb02HAGNrSQSumCe10WnOAcnM6bR6KMatQyQJuEu7OY6ZDSTM/4HfjjoxsNqdvPmbYuoUab1bKTNlA==
|
integrity sha512-S3tRvOdA9amPkKLll3rc4vphlDitLrkN5TwWh5Tu/jzk7mnobVVE3akYgICV9XCNHKjWM+IAPxFFI2qi+VW6nQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/lexicon" "^0.4.1"
|
"@atproto/lexicon" "^0.4.2"
|
||||||
zod "^3.23.8"
|
zod "^3.23.8"
|
||||||
|
|
||||||
"@aws-crypto/crc32@3.0.0":
|
"@aws-crypto/crc32@3.0.0":
|
||||||
|
|||||||
Reference in New Issue
Block a user