This commit is contained in:
Eric Bailey
2024-08-12 15:33:50 -05:00
parent 7adaf159c1
commit 5d78f69d95
8 changed files with 260 additions and 345 deletions
+27 -151
View File
@@ -2,32 +2,20 @@ import React from 'react'
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
import {
AppBskyFeedDefs,
AppBskyFeedGetPostThread,
AppBskyFeedPostgate,
AppBskyFeedPost,
AppBskyGraphDefs,
AtUri,
BskyAgent,
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {until} from '#/lib/async/until'
import {HITSLOP_10} from '#/lib/constants'
import {makeListLink, makeProfileLink} from '#/lib/routes/links'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread'
import {useWritePostgateMutation} from '#/state/queries/postgate'
import {embeddingRules} from '#/state/queries/postgate/util'
import {
ThreadgateAllowUISetting,
threadgateRecordQueryKeyRoot,
threadgateViewToAllowUISetting,
updateThreadgateAllow,
} from '#/state/queries/threadgate'
import {useAgent} from '#/state/session'
import * as Toast from 'view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -44,29 +32,25 @@ interface WhoCanReplyProps {
post: AppBskyFeedDefs.PostView
isThreadAuthor: boolean
style?: StyleProp<ViewStyle>
postgate: AppBskyFeedPostgate.Record
}
export function WhoCanReply({
post,
isThreadAuthor,
style,
postgate: initialPostgate,
}: WhoCanReplyProps) {
export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
const {_} = useLingui()
const t = useTheme()
const infoDialogControl = useDialogControl()
const editDialogControl = useDialogControl()
const agent = useAgent()
const queryClient = useQueryClient()
const [isSaving, setIsSaving] = React.useState(false)
const {mutateAsync: writePostgateRecord} = useWritePostgateMutation()
const [postgate, setPostgate] = React.useState(initialPostgate)
// TODO test if we get weird data back
const [settings, setSettings] = React.useState(
threadgateViewToAllowUISetting(post.threadgate),
)
/*
* `WhoCanReply` is only used for root posts atm, in case this changes
* unexpectedly, we should check to make sure it's for sure the root URI.
*/
const rootUri =
AppBskyFeedPost.isRecord(post.record) && post.record.reply?.root
? post.record.reply.root.uri
: post.uri
const settings = React.useMemo(() => {
return threadgateViewToAllowUISetting(post.threadgate)
}, [post.threadgate])
const anyoneCanReply =
settings.length === 1 && settings[0].type === 'everybody'
@@ -88,95 +72,6 @@ export function WhoCanReply({
}
}
const onChangePostgate = React.useCallback(
(next: AppBskyFeedPostgate.Record) => {
setPostgate(next)
},
[setPostgate],
)
const onChangeThreadgateAllowUISettings = React.useCallback(
(next: ThreadgateAllowUISetting[]) => {
setSettings(next)
},
[setSettings],
)
const saveThreadgateAllowSettings = React.useCallback(async () => {
try {
await updateThreadgateAllow({
agent,
postUri: post.uri,
allow: settings,
})
await whenAppViewReady(agent, post.uri, res => {
const thread = res.data.thread
if (AppBskyFeedDefs.isThreadViewPost(thread)) {
const fetchedSettings = threadgateViewToAllowUISetting(
thread.post.threadgate,
)
return JSON.stringify(fetchedSettings) === JSON.stringify(settings)
}
return false
})
queryClient.invalidateQueries({
queryKey: [POST_THREAD_RQKEY_ROOT],
})
queryClient.invalidateQueries({
queryKey: [threadgateRecordQueryKeyRoot],
})
} catch (e: any) {
logger.error('Failed to edit threadgate', {safeMessage: e.message})
Toast.show(
_(
msg`There was an issue. Please check your internet connection and try again.`,
),
'xmark',
)
}
}, [_, agent, post, settings, queryClient])
const savePostgateRecord = React.useCallback(async () => {
try {
await writePostgateRecord({postUri: post.uri, postgate})
} catch (e: any) {
logger.error('Failed to save postgate', {safeMessage: e.message})
Toast.show(
_(
msg`There was an issue. Please check your internet connection and try again.`,
),
'xmark',
)
}
}, [_, post, postgate, writePostgateRecord])
const onSave = React.useCallback(async () => {
setIsSaving(true)
try {
await Promise.all([saveThreadgateAllowSettings(), savePostgateRecord()])
editDialogControl.close()
Toast.show(_(msg`Thread settings updated`))
} catch (e: any) {
Toast.show(
_(
msg`There was an issue. Please check your internet connection and try again.`,
),
'xmark',
)
} finally {
setIsSaving(false)
}
}, [
_,
editDialogControl,
saveThreadgateAllowSettings,
savePostgateRecord,
setIsSaving,
])
return (
<>
<Button
@@ -211,20 +106,17 @@ export function WhoCanReply({
{isThreadAuthor ? (
<PostInteractionSettingsDialog
onSave={onSave}
isSaving={isSaving}
postgate={postgate}
onChangePostgate={onChangePostgate}
threadgateAllowUISettings={settings}
onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings}
postUri={post.uri}
rootPostUri={rootUri}
control={editDialogControl}
initialThreadgateView={post.threadgate}
/>
) : (
<WhoCanReplyDialog
control={infoDialogControl}
post={post}
settings={settings}
postgate={postgate}
embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)}
/>
)}
</>
@@ -250,12 +142,12 @@ function WhoCanReplyDialog({
control,
post,
settings,
postgate,
embeddingDisabled,
}: {
control: Dialog.DialogControlProps
post: AppBskyFeedDefs.PostView
settings: ThreadgateAllowUISetting[]
postgate: AppBskyFeedPostgate.Record
embeddingDisabled: boolean
}) {
const {_} = useLingui()
return (
@@ -268,7 +160,11 @@ function WhoCanReplyDialog({
<Text style={[a.font_bold, a.text_xl, a.pb_sm]}>
<Trans>Who can interact with this post?</Trans>
</Text>
<Rules post={post} settings={settings} postgate={postgate} />
<Rules
post={post}
settings={settings}
embeddingDisabled={embeddingDisabled}
/>
</View>
</Dialog.ScrollableInner>
</Dialog.Outer>
@@ -278,16 +174,13 @@ function WhoCanReplyDialog({
function Rules({
post,
settings,
postgate,
embeddingDisabled,
}: {
post: AppBskyFeedDefs.PostView
settings: ThreadgateAllowUISetting[]
postgate: AppBskyFeedPostgate.Record
embeddingDisabled: boolean
}) {
const t = useTheme()
const noOneCanQuote =
postgate.embeddingRules?.length === 1 &&
postgate.embeddingRules[0]?.$type === embeddingRules.disableRule.$type
return (
<>
@@ -320,7 +213,7 @@ function Rules({
</Trans>
)}{' '}
</Text>
{noOneCanQuote && (
{embeddingDisabled && (
<Text
style={[
a.text_sm,
@@ -392,20 +285,3 @@ function Separator({i, length}: {i: number; length: number}) {
}
return <>, </>
}
async function whenAppViewReady(
agent: BskyAgent,
uri: string,
fn: (res: AppBskyFeedGetPostThread.Response) => boolean,
) {
await until(
5, // 5 tries
1e3, // 1s delay between tries
fn,
() =>
agent.app.bsky.feed.getPostThread({
uri,
depth: 0,
}),
)
}
@@ -1,28 +1,46 @@
import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native'
import {AppBskyFeedPostgate} from '@atproto/api'
import {
AppBskyFeedDefs,
AppBskyFeedPostgate,
AppBskyFeedThreadgate,
AtUri,
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import isEqual from 'lodash.isequal'
import {logger} from '#/logger'
import {useMyListsQuery} from '#/state/queries/my-lists'
import {
usePostgateQuery,
useWritePostgateMutation,
} from '#/state/queries/postgate'
import {
createPostgateRecord,
embeddingRules,
} from '#/state/queries/postgate/util'
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate'
import {
ThreadgateAllowUISetting,
threadgateViewToAllowUISetting,
useSetThreadgateAllowMutation,
useThreadgateViewQuery,
} from '#/state/queries/threadgate'
import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/toast'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {Divider} from '#/components/Divider'
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
export type Props = {
export type PostInteractionSettingsFormProps = {
onSave: () => void
isSaving: boolean
isSaving?: boolean
postgate: AppBskyFeedPostgate.Record
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
@@ -33,10 +51,10 @@ export type Props = {
replySettingsDisabled?: boolean
}
export function PostInteractionSettingsDialog({
export function PostInteractionSettingsControlledDialog({
control,
...rest
}: Props & {
}: PostInteractionSettingsFormProps & {
control: Dialog.DialogControlProps
}) {
const {_} = useLingui()
@@ -46,14 +64,168 @@ export function PostInteractionSettingsDialog({
<Dialog.ScrollableInner
label={_(msg`Edit post interaction settings`)}
style={[{maxWidth: 500}, a.w_full]}>
<PostInteractionSettingsDialogInner {...rest} />
<PostInteractionSettingsForm {...rest} />
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
export function PostInteractionSettingsDialogInner({
export type PostInteractionSettingsDialogProps = {
control: Dialog.DialogControlProps
/**
* URI of the post to edit the interaction settings for. Could be a root post
* or could be a reply.
*/
postUri: string
/**
* The URI of the root post in the thread. Used to determine if the viewer
* owns the threadgate record and can therefore edit it.
*/
rootPostUri: string
/**
* Optional initial {@link AppBskyFeedDefs.ThreadgateView} to use if we
* happen to have one before opening the settings dialog.
*/
initialThreadgateView?: AppBskyFeedDefs.ThreadgateView
}
export function PostInteractionSettingsDialog(
props: PostInteractionSettingsDialogProps,
) {
return (
<Dialog.Outer control={props.control}>
<Dialog.Handle />
<PostInteractionSettingsDialogControlledInner {...props} />
</Dialog.Outer>
)
}
export function PostInteractionSettingsDialogControlledInner(
props: PostInteractionSettingsDialogProps,
) {
const {_} = useLingui()
const {currentAccount} = useSession()
const [isSaving, setIsSaving] = React.useState(false)
const {data: threadgateViewLoaded, isLoading: isLoadingThreadgate} =
useThreadgateViewQuery({postUri: props.rootPostUri})
const {data: postgate, isLoading: isLoadingPostgate} = usePostgateQuery({
postUri: props.postUri,
})
const {mutateAsync: writePostgateRecord} = useWritePostgateMutation()
const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation()
const naturalLoading = useDelayedLoading(500) // TODO
const [editedPostgate, setEditedPostgate] =
React.useState<AppBskyFeedPostgate.Record>()
const [editedAllowUISettings, setEditedAllowUISettings] =
React.useState<ThreadgateAllowUISetting[]>()
const isLoading = isLoadingThreadgate || isLoadingPostgate || naturalLoading
const threadgateView = threadgateViewLoaded || props.initialThreadgateView
const isThreadgateOwnedByViewer = React.useMemo(() => {
if (AppBskyFeedThreadgate.isRecord(threadgateView?.record)) {
return (
currentAccount?.did === new AtUri(threadgateView?.record?.post).host
)
}
return false
}, [threadgateView, currentAccount?.did])
const postgateValue = React.useMemo(() => {
return (
editedPostgate || postgate || createPostgateRecord({post: props.postUri})
)
}, [postgate, editedPostgate, props.postUri])
const allowUIValue = React.useMemo(() => {
return (
editedAllowUISettings || threadgateViewToAllowUISetting(threadgateView)
)
}, [threadgateView, editedAllowUISettings])
const onSave = React.useCallback(async () => {
if (!editedPostgate && !editedAllowUISettings) {
props.control.close()
return
}
setIsSaving(true)
try {
const requests = []
if (editedPostgate) {
requests.push(
writePostgateRecord({
postUri: props.postUri,
postgate: editedPostgate,
}),
)
}
if (editedAllowUISettings && isThreadgateOwnedByViewer) {
requests.push(
setThreadgateAllow({
postUri: props.rootPostUri,
allow: editedAllowUISettings,
}),
)
}
await Promise.all(requests)
props.control.close()
} catch (e: any) {
logger.error(`Failed to save post interaction settings`, {
context: 'PostInteractionSettingsDialogControlledInner',
safeMessage: e.message,
})
Toast.show(
_(
msg`There was an issue. Please check your internet connection and try again.`,
),
'xmark',
)
} finally {
setIsSaving(false)
}
}, [
_,
props.postUri,
props.rootPostUri,
props.control,
editedPostgate,
editedAllowUISettings,
setIsSaving,
writePostgateRecord,
setThreadgateAllow,
isThreadgateOwnedByViewer,
])
return (
<Dialog.ScrollableInner
label={_(msg`Edit post interaction settings`)}
style={[{maxWidth: 500}, a.w_full]}>
{isLoading ? (
<Loader size="xl" />
) : (
<PostInteractionSettingsForm
replySettingsDisabled={!isThreadgateOwnedByViewer}
isSaving={isSaving}
onSave={onSave}
postgate={postgateValue}
onChangePostgate={setEditedPostgate}
threadgateAllowUISettings={allowUIValue}
onChangeThreadgateAllowUISettings={setEditedAllowUISettings}
/>
)}
</Dialog.ScrollableInner>
)
}
export function PostInteractionSettingsForm({
onSave,
isSaving,
postgate,
@@ -61,7 +233,7 @@ export function PostInteractionSettingsDialogInner({
threadgateAllowUISettings,
onChangeThreadgateAllowUISettings,
replySettingsDisabled,
}: Props) {
}: PostInteractionSettingsFormProps) {
const t = useTheme()
const {_} = useLingui()
const control = Dialog.useDialogContext()
+1
View File
@@ -76,6 +76,7 @@ export function useThreadgateViewQuery({
uri: postUri!,
depth: 0,
})
console.log(data.thread)
if (AppBskyFeedDefs.isThreadViewPost(data.thread)) {
return data.thread.post.threadgate ?? null
@@ -11,7 +11,7 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {PostInteractionSettingsDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
@@ -71,9 +71,8 @@ export function ThreadgateBtn({
<ButtonText>{label}</ButtonText>
</Button>
</Animated.View>
<PostInteractionSettingsDialog
<PostInteractionSettingsControlledDialog
control={control}
isSaving={false}
onSave={() => {
control.close()
}}
+1
View File
@@ -443,6 +443,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
<PostThreadItem
post={item.post}
record={item.record}
threadgateRecord={threadgateRecord ?? undefined}
moderation={threadModerationCache.get(item)}
treeView={treeView}
depth={item.ctx.depth}
+10 -30
View File
@@ -3,7 +3,7 @@ import {StyleSheet, View} from 'react-native'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
AppBskyFeedPostgate,
AppBskyFeedThreadgate,
AtUri,
ModerationDecision,
RichText as RichTextAPI,
@@ -16,9 +16,6 @@ import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
import {useLanguagePrefs} from '#/state/preferences'
import {useOpenLink} from '#/state/preferences/in-app-browser'
import {ThreadPost} from '#/state/queries/post-thread'
import {usePostgateQuery} from '#/state/queries/postgate'
import {createPostgateRecord} from '#/state/queries/postgate/util'
import {useThreadgateRecordQuery} from '#/state/queries/threadgate'
import {useComposerControls} from '#/state/shell/composer'
import {MAX_POST_LINES} from 'lib/constants'
import {usePalette} from 'lib/hooks/usePalette'
@@ -66,6 +63,7 @@ export function PostThreadItem({
overrideBlur,
onPostReply,
hideTopBorder,
threadgateRecord,
}: {
post: AppBskyFeedDefs.PostView
record: AppBskyFeedPost.Record
@@ -82,9 +80,9 @@ export function PostThreadItem({
overrideBlur: boolean
onPostReply: (postUri: string | undefined) => void
hideTopBorder?: boolean
threadgateRecord?: AppBskyFeedThreadgate.Record
}) {
// TODO should load this for every post, ugh
const {data: postgate, isLoading} = usePostgateQuery({postUri: post.uri})
const postShadowed = usePostShadow(post)
const richText = useMemo(
() =>
@@ -97,7 +95,7 @@ export function PostThreadItem({
if (postShadowed === POST_TOMBSTONE) {
return <PostThreadItemDeleted hideTopBorder={hideTopBorder} />
}
if (richText && moderation && !isLoading) {
if (richText && moderation) {
return (
<PostThreadItemLoaded
// Safeguard from clobbering per-post state below:
@@ -118,7 +116,7 @@ export function PostThreadItem({
overrideBlur={overrideBlur}
onPostReply={onPostReply}
hideTopBorder={hideTopBorder}
postgate={postgate}
threadgateRecord={threadgateRecord}
/>
)
}
@@ -162,7 +160,7 @@ let PostThreadItemLoaded = ({
overrideBlur,
onPostReply,
hideTopBorder,
postgate,
threadgateRecord,
}: {
post: Shadow<AppBskyFeedDefs.PostView>
record: AppBskyFeedPost.Record
@@ -180,7 +178,7 @@ let PostThreadItemLoaded = ({
overrideBlur: boolean
onPostReply: (postUri: string | undefined) => void
hideTopBorder?: boolean
postgate: AppBskyFeedPostgate.Record | undefined
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const pal = usePalette('default')
const {_} = useLingui()
@@ -209,9 +207,6 @@ let PostThreadItemLoaded = ({
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
}, [post.uri, post.author])
const repostsTitle = _(msg`Reposts of this post`)
const {data: threadgateRecord} = useThreadgateRecordQuery({
postUri: rootUri,
})
const isPostHiddenByThreadgate = threadgateRecord?.hiddenReplies?.includes(
post.uri,
)
@@ -370,7 +365,6 @@ let PostThreadItemLoaded = ({
isThreadAuthor={isThreadAuthor}
translatorUrl={translatorUrl}
needsTranslation={needsTranslation}
postgate={postgate}
/>
{post.repostCount !== 0 || post.likeCount !== 0 ? (
// Show this section unless we're *sure* it has no engagement.
@@ -421,8 +415,7 @@ let PostThreadItemLoaded = ({
richText={richText}
onPressReply={onPressReply}
logContext="PostThreadItem"
rootPostUri={rootUri}
threadgateRecord={threadgateRecord ?? undefined}
threadgateRecord={threadgateRecord}
/>
</View>
</View>
@@ -575,8 +568,7 @@ let PostThreadItemLoaded = ({
richText={richText}
onPressReply={onPressReply}
logContext="PostThreadItem"
rootPostUri={rootUri}
threadgateRecord={threadgateRecord ?? undefined}
threadgateRecord={threadgateRecord}
/>
</View>
</View>
@@ -674,13 +666,11 @@ function ExpandedPostDetails({
isThreadAuthor,
needsTranslation,
translatorUrl,
postgate,
}: {
post: AppBskyFeedDefs.PostView
isThreadAuthor: boolean
needsTranslation: boolean
translatorUrl: string
postgate: AppBskyFeedPostgate.Record | undefined
}) {
const pal = usePalette('default')
const {_} = useLingui()
@@ -703,17 +693,7 @@ function ExpandedPostDetails({
]}>
<Text style={[a.text_sm, pal.textLight]}>{niceDate(post.indexedAt)}</Text>
{isRootPost && (
<WhoCanReply
post={post}
postgate={
// TODO maybe define at query
postgate ||
createPostgateRecord({
post: post.uri,
})
}
isThreadAuthor={isThreadAuthor}
/>
<WhoCanReply post={post} isThreadAuthor={isThreadAuthor} />
)}
{needsTranslation && (
<>
+38 -149
View File
@@ -9,7 +9,6 @@ import * as Clipboard from 'expo-clipboard'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
AppBskyFeedPostgate,
AppBskyFeedThreadgate,
AtUri,
RichText as RichTextAPI,
@@ -33,17 +32,9 @@ import {
usePostDeleteMutation,
useThreadMuteMutationQueue,
} from '#/state/queries/post'
import {
usePostgateQuery,
useToggleQuoteDetachmentMutation,
useWritePostgateMutation,
} from '#/state/queries/postgate'
import {
createPostgateRecord,
getMaybeDetachedQuoteEmbed,
} from '#/state/queries/postgate/util'
import {useToggleQuoteDetachmentMutation} from '#/state/queries/postgate'
import {getMaybeDetachedQuoteEmbed} from '#/state/queries/postgate/util'
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate/util'
import {useSession} from '#/state/session'
import {getCurrentRoute} from 'lib/routes/helpers'
import {shareUrl} from 'lib/sharing'
@@ -51,10 +42,9 @@ import {toShareUrl} from 'lib/strings/url-helpers'
import {useTheme} from 'lib/ThemeContext'
import {atoms as a, useBreakpoints, useTheme as useAlf} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import * as Dialog from '#/components/Dialog'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
import {EmbedDialog} from '#/components/dialogs/Embed'
import {PostInteractionSettingsDialogInner} from '#/components/dialogs/PostInteractionSettingsDialog'
import {PostInteractionSettingsDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
@@ -91,7 +81,6 @@ let PostDropdownBtn = ({
hitSlop,
size,
timestamp,
rootPostUri,
threadgateRecord,
}: {
testID: string
@@ -103,7 +92,6 @@ let PostDropdownBtn = ({
hitSlop?: PressableProps['hitSlop']
size?: 'lg' | 'md' | 'sm'
timestamp: string
rootPostUri?: string
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const {hasSession, currentAccount} = useSession()
@@ -282,36 +270,6 @@ let PostDropdownBtn = ({
[navigation, postUri],
)
const onToggleReplyVisibility = React.useCallback(async () => {
// TODO no threadgate?
if (!rootPostUri) return
const action = isReplyHiddenByThreadgate ? 'show' : 'hide'
const isHide = action === 'hide'
try {
await toggleReplyVisibility({
postUri: rootPostUri,
replyUri: postUri,
action,
})
Toast.show(
isHide
? _(msg`Reply was successfully hidden`)
: _(msg`Reply visibility updated`),
)
} catch (e: any) {
Toast.show(_(msg`Updating reply visibility failed`))
logger.error(`Failed to ${action} reply`, {safeMessage: e.message})
}
}, [
_,
isReplyHiddenByThreadgate,
rootPostUri,
postUri,
toggleReplyVisibility,
])
const onToggleQuotePostAttachment = React.useCallback(async () => {
if (!quoteEmbed) return
@@ -336,10 +294,40 @@ let PostDropdownBtn = ({
}, [_, quoteEmbed, post, toggleQuoteDetachment])
const canEmbed = isWeb && gtMobile && !hideInPWI
const canHideReply =
!isAuthor && isRootPostAuthor && !isPostHidden && rootPostUri && isReply
const canHideReply = !isAuthor && isRootPostAuthor && !isPostHidden && isReply
const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer
const onToggleReplyVisibility = React.useCallback(async () => {
// TODO no threadgate?
if (!canHideReply) return
const action = isReplyHiddenByThreadgate ? 'show' : 'hide'
const isHide = action === 'hide'
try {
await toggleReplyVisibility({
postUri: rootUri,
replyUri: postUri,
action,
})
Toast.show(
isHide
? _(msg`Reply was successfully hidden`)
: _(msg`Reply visibility updated`),
)
} catch (e: any) {
Toast.show(_(msg`Updating reply visibility failed`))
logger.error(`Failed to ${action} reply`, {safeMessage: e.message})
}
}, [
_,
isReplyHiddenByThreadgate,
rootUri,
postUri,
canHideReply,
toggleReplyVisibility,
])
return (
<EventStopper onKeyDown={false}>
<Menu.Root>
@@ -643,114 +631,15 @@ let PostDropdownBtn = ({
onSelectChat={onSelectChatToShareTo}
/>
<PostInteractionSettingsDialogControlled
<PostInteractionSettingsDialog
control={postInteractionSettingsDialogControl}
postUri={post.uri}
threadgateRecord={threadgateRecord}
replySettingsDisabled={!isRootPostAuthor}
rootPostUri={rootUri}
initialThreadgateView={post.threadgate}
/>
</EventStopper>
)
}
export function PostInteractionSettingsDialogControlled(props: {
control: Dialog.DialogControlProps
postUri: string
threadgateRecord?: AppBskyFeedThreadgate.Record
replySettingsDisabled: boolean
}) {
return (
<Dialog.Outer control={props.control}>
<Dialog.Handle />
<PostInteractionSettingsDialogControlledInner {...props} />
</Dialog.Outer>
)
}
function PostInteractionSettingsDialogControlledInner(props: {
control: Dialog.DialogControlProps
postUri: string
threadgateRecord?: AppBskyFeedThreadgate.Record
replySettingsDisabled: boolean
}) {
const {_} = useLingui()
const threadgateAllowUISettings = React.useMemo(() => {
return threadgateRecordToAllowUISetting(props.threadgateRecord)
}, [props.threadgateRecord])
const [isSaving, setIsSaving] = React.useState(false)
const {mutateAsync: writePostgateRecord} = useWritePostgateMutation()
const {data: postgate, isLoading} = usePostgateQuery({postUri: props.postUri})
const [editedPostgate, setEditedPostgate] =
React.useState<AppBskyFeedPostgate.Record>()
const onChangePostgate = React.useCallback(
(next: AppBskyFeedPostgate.Record) => {
setEditedPostgate(next)
},
[setEditedPostgate],
)
const onSave = React.useCallback(async () => {
if (!editedPostgate) {
props.control.close()
return
}
setIsSaving(true)
try {
await writePostgateRecord({
postUri: props.postUri,
postgate: editedPostgate,
})
props.control.close()
} catch (e: any) {
logger.error(`Failed to save post interaction settings`, {
context: 'PostInteractionSettingsDialogControlledInner',
safeMessage: e.message,
})
Toast.show(
_(
msg`There was an issue. Please check your internet connection and try again.`,
),
'xmark',
)
} finally {
setIsSaving(false)
}
}, [
_,
props.postUri,
props.control,
editedPostgate,
setIsSaving,
writePostgateRecord,
])
return (
<Dialog.ScrollableInner
label={_(msg`Edit post interaction settings`)}
style={[{maxWidth: 500}, a.w_full]}>
{isLoading ? (
<Loader size="xl" />
) : (
<PostInteractionSettingsDialogInner
replySettingsDisabled={props.replySettingsDisabled}
isSaving={isSaving}
onSave={onSave}
postgate={
editedPostgate ||
postgate ||
createPostgateRecord({post: props.postUri})
}
onChangePostgate={onChangePostgate}
threadgateAllowUISettings={threadgateAllowUISettings}
onChangeThreadgateAllowUISettings={() => {}}
/>
)}
</Dialog.ScrollableInner>
)
}
PostDropdownBtn = memo(PostDropdownBtn)
export {PostDropdownBtn}
@@ -60,7 +60,6 @@ let PostCtrls = ({
style,
onPressReply,
logContext,
rootPostUri,
threadgateRecord,
}: {
big?: boolean
@@ -71,7 +70,6 @@ let PostCtrls = ({
style?: StyleProp<ViewStyle>
onPressReply: () => void
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
rootPostUri?: string
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const t = useTheme()
@@ -344,7 +342,6 @@ let PostCtrls = ({
style={{padding: 5}}
hitSlop={POST_CTRL_HITSLOP}
timestamp={post.indexedAt}
rootPostUri={rootPostUri}
threadgateRecord={threadgateRecord}
/>
</View>