Plumbing
This commit is contained in:
+27
-151
@@ -2,32 +2,20 @@ import React from 'react'
|
|||||||
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
|
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedGetPostThread,
|
AppBskyFeedPost,
|
||||||
AppBskyFeedPostgate,
|
|
||||||
AppBskyGraphDefs,
|
AppBskyGraphDefs,
|
||||||
AtUri,
|
AtUri,
|
||||||
BskyAgent,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {until} from '#/lib/async/until'
|
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
import {HITSLOP_10} from '#/lib/constants'
|
||||||
import {makeListLink, makeProfileLink} from '#/lib/routes/links'
|
import {makeListLink, makeProfileLink} from '#/lib/routes/links'
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {isNative} from '#/platform/detection'
|
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 {
|
import {
|
||||||
ThreadgateAllowUISetting,
|
ThreadgateAllowUISetting,
|
||||||
threadgateRecordQueryKeyRoot,
|
|
||||||
threadgateViewToAllowUISetting,
|
threadgateViewToAllowUISetting,
|
||||||
updateThreadgateAllow,
|
|
||||||
} from '#/state/queries/threadgate'
|
} 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 {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
@@ -44,29 +32,25 @@ interface WhoCanReplyProps {
|
|||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
isThreadAuthor: boolean
|
isThreadAuthor: boolean
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
postgate: AppBskyFeedPostgate.Record
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WhoCanReply({
|
export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
|
||||||
post,
|
|
||||||
isThreadAuthor,
|
|
||||||
style,
|
|
||||||
postgate: initialPostgate,
|
|
||||||
}: WhoCanReplyProps) {
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const infoDialogControl = useDialogControl()
|
const infoDialogControl = useDialogControl()
|
||||||
const editDialogControl = 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
|
* `WhoCanReply` is only used for root posts atm, in case this changes
|
||||||
const [settings, setSettings] = React.useState(
|
* unexpectedly, we should check to make sure it's for sure the root URI.
|
||||||
threadgateViewToAllowUISetting(post.threadgate),
|
*/
|
||||||
)
|
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 =
|
const anyoneCanReply =
|
||||||
settings.length === 1 && settings[0].type === 'everybody'
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -211,20 +106,17 @@ export function WhoCanReply({
|
|||||||
|
|
||||||
{isThreadAuthor ? (
|
{isThreadAuthor ? (
|
||||||
<PostInteractionSettingsDialog
|
<PostInteractionSettingsDialog
|
||||||
onSave={onSave}
|
postUri={post.uri}
|
||||||
isSaving={isSaving}
|
rootPostUri={rootUri}
|
||||||
postgate={postgate}
|
|
||||||
onChangePostgate={onChangePostgate}
|
|
||||||
threadgateAllowUISettings={settings}
|
|
||||||
onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings}
|
|
||||||
control={editDialogControl}
|
control={editDialogControl}
|
||||||
|
initialThreadgateView={post.threadgate}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<WhoCanReplyDialog
|
<WhoCanReplyDialog
|
||||||
control={infoDialogControl}
|
control={infoDialogControl}
|
||||||
post={post}
|
post={post}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
postgate={postgate}
|
embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -250,12 +142,12 @@ function WhoCanReplyDialog({
|
|||||||
control,
|
control,
|
||||||
post,
|
post,
|
||||||
settings,
|
settings,
|
||||||
postgate,
|
embeddingDisabled,
|
||||||
}: {
|
}: {
|
||||||
control: Dialog.DialogControlProps
|
control: Dialog.DialogControlProps
|
||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
settings: ThreadgateAllowUISetting[]
|
settings: ThreadgateAllowUISetting[]
|
||||||
postgate: AppBskyFeedPostgate.Record
|
embeddingDisabled: boolean
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
return (
|
return (
|
||||||
@@ -268,7 +160,11 @@ function WhoCanReplyDialog({
|
|||||||
<Text style={[a.font_bold, a.text_xl, a.pb_sm]}>
|
<Text style={[a.font_bold, a.text_xl, a.pb_sm]}>
|
||||||
<Trans>Who can interact with this post?</Trans>
|
<Trans>Who can interact with this post?</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Rules post={post} settings={settings} postgate={postgate} />
|
<Rules
|
||||||
|
post={post}
|
||||||
|
settings={settings}
|
||||||
|
embeddingDisabled={embeddingDisabled}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
@@ -278,16 +174,13 @@ function WhoCanReplyDialog({
|
|||||||
function Rules({
|
function Rules({
|
||||||
post,
|
post,
|
||||||
settings,
|
settings,
|
||||||
postgate,
|
embeddingDisabled,
|
||||||
}: {
|
}: {
|
||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
settings: ThreadgateAllowUISetting[]
|
settings: ThreadgateAllowUISetting[]
|
||||||
postgate: AppBskyFeedPostgate.Record
|
embeddingDisabled: boolean
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const noOneCanQuote =
|
|
||||||
postgate.embeddingRules?.length === 1 &&
|
|
||||||
postgate.embeddingRules[0]?.$type === embeddingRules.disableRule.$type
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -320,7 +213,7 @@ function Rules({
|
|||||||
</Trans>
|
</Trans>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
</Text>
|
</Text>
|
||||||
{noOneCanQuote && (
|
{embeddingDisabled && (
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
@@ -392,20 +285,3 @@ function Separator({i, length}: {i: number; length: number}) {
|
|||||||
}
|
}
|
||||||
return <>, </>
|
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 React from 'react'
|
||||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
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 {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from 'lodash.isequal'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {useMyListsQuery} from '#/state/queries/my-lists'
|
import {useMyListsQuery} from '#/state/queries/my-lists'
|
||||||
|
import {
|
||||||
|
usePostgateQuery,
|
||||||
|
useWritePostgateMutation,
|
||||||
|
} from '#/state/queries/postgate'
|
||||||
import {
|
import {
|
||||||
createPostgateRecord,
|
createPostgateRecord,
|
||||||
embeddingRules,
|
embeddingRules,
|
||||||
} from '#/state/queries/postgate/util'
|
} 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 {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
|
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
|
||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export type Props = {
|
export type PostInteractionSettingsFormProps = {
|
||||||
onSave: () => void
|
onSave: () => void
|
||||||
isSaving: boolean
|
isSaving?: boolean
|
||||||
|
|
||||||
postgate: AppBskyFeedPostgate.Record
|
postgate: AppBskyFeedPostgate.Record
|
||||||
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
|
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
|
||||||
@@ -33,10 +51,10 @@ export type Props = {
|
|||||||
replySettingsDisabled?: boolean
|
replySettingsDisabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostInteractionSettingsDialog({
|
export function PostInteractionSettingsControlledDialog({
|
||||||
control,
|
control,
|
||||||
...rest
|
...rest
|
||||||
}: Props & {
|
}: PostInteractionSettingsFormProps & {
|
||||||
control: Dialog.DialogControlProps
|
control: Dialog.DialogControlProps
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -46,14 +64,168 @@ export function PostInteractionSettingsDialog({
|
|||||||
<Dialog.ScrollableInner
|
<Dialog.ScrollableInner
|
||||||
label={_(msg`Edit post interaction settings`)}
|
label={_(msg`Edit post interaction settings`)}
|
||||||
style={[{maxWidth: 500}, a.w_full]}>
|
style={[{maxWidth: 500}, a.w_full]}>
|
||||||
<PostInteractionSettingsDialogInner {...rest} />
|
<PostInteractionSettingsForm {...rest} />
|
||||||
<Dialog.Close />
|
<Dialog.Close />
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
</Dialog.Outer>
|
</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,
|
onSave,
|
||||||
isSaving,
|
isSaving,
|
||||||
postgate,
|
postgate,
|
||||||
@@ -61,7 +233,7 @@ export function PostInteractionSettingsDialogInner({
|
|||||||
threadgateAllowUISettings,
|
threadgateAllowUISettings,
|
||||||
onChangeThreadgateAllowUISettings,
|
onChangeThreadgateAllowUISettings,
|
||||||
replySettingsDisabled,
|
replySettingsDisabled,
|
||||||
}: Props) {
|
}: PostInteractionSettingsFormProps) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function useThreadgateViewQuery({
|
|||||||
uri: postUri!,
|
uri: postUri!,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
})
|
})
|
||||||
|
console.log(data.thread)
|
||||||
|
|
||||||
if (AppBskyFeedDefs.isThreadViewPost(data.thread)) {
|
if (AppBskyFeedDefs.isThreadViewPost(data.thread)) {
|
||||||
return data.thread.post.threadgate ?? null
|
return data.thread.post.threadgate ?? null
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {useAnalytics} from 'lib/analytics/analytics'
|
|||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
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 {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
||||||
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
||||||
|
|
||||||
@@ -71,9 +71,8 @@ export function ThreadgateBtn({
|
|||||||
<ButtonText>{label}</ButtonText>
|
<ButtonText>{label}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
<PostInteractionSettingsDialog
|
<PostInteractionSettingsControlledDialog
|
||||||
control={control}
|
control={control}
|
||||||
isSaving={false}
|
|
||||||
onSave={() => {
|
onSave={() => {
|
||||||
control.close()
|
control.close()
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -443,6 +443,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
<PostThreadItem
|
<PostThreadItem
|
||||||
post={item.post}
|
post={item.post}
|
||||||
record={item.record}
|
record={item.record}
|
||||||
|
threadgateRecord={threadgateRecord ?? undefined}
|
||||||
moderation={threadModerationCache.get(item)}
|
moderation={threadModerationCache.get(item)}
|
||||||
treeView={treeView}
|
treeView={treeView}
|
||||||
depth={item.ctx.depth}
|
depth={item.ctx.depth}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {StyleSheet, View} from 'react-native'
|
|||||||
import {
|
import {
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AppBskyFeedPostgate,
|
AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
ModerationDecision,
|
ModerationDecision,
|
||||||
RichText as RichTextAPI,
|
RichText as RichTextAPI,
|
||||||
@@ -16,9 +16,6 @@ import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
|||||||
import {useLanguagePrefs} from '#/state/preferences'
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||||
import {ThreadPost} from '#/state/queries/post-thread'
|
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 {useComposerControls} from '#/state/shell/composer'
|
||||||
import {MAX_POST_LINES} from 'lib/constants'
|
import {MAX_POST_LINES} from 'lib/constants'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
@@ -66,6 +63,7 @@ export function PostThreadItem({
|
|||||||
overrideBlur,
|
overrideBlur,
|
||||||
onPostReply,
|
onPostReply,
|
||||||
hideTopBorder,
|
hideTopBorder,
|
||||||
|
threadgateRecord,
|
||||||
}: {
|
}: {
|
||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
record: AppBskyFeedPost.Record
|
record: AppBskyFeedPost.Record
|
||||||
@@ -82,9 +80,9 @@ export function PostThreadItem({
|
|||||||
overrideBlur: boolean
|
overrideBlur: boolean
|
||||||
onPostReply: (postUri: string | undefined) => void
|
onPostReply: (postUri: string | undefined) => void
|
||||||
hideTopBorder?: boolean
|
hideTopBorder?: boolean
|
||||||
|
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||||
}) {
|
}) {
|
||||||
// TODO should load this for every post, ugh
|
// TODO should load this for every post, ugh
|
||||||
const {data: postgate, isLoading} = usePostgateQuery({postUri: post.uri})
|
|
||||||
const postShadowed = usePostShadow(post)
|
const postShadowed = usePostShadow(post)
|
||||||
const richText = useMemo(
|
const richText = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -97,7 +95,7 @@ export function PostThreadItem({
|
|||||||
if (postShadowed === POST_TOMBSTONE) {
|
if (postShadowed === POST_TOMBSTONE) {
|
||||||
return <PostThreadItemDeleted hideTopBorder={hideTopBorder} />
|
return <PostThreadItemDeleted hideTopBorder={hideTopBorder} />
|
||||||
}
|
}
|
||||||
if (richText && moderation && !isLoading) {
|
if (richText && moderation) {
|
||||||
return (
|
return (
|
||||||
<PostThreadItemLoaded
|
<PostThreadItemLoaded
|
||||||
// Safeguard from clobbering per-post state below:
|
// Safeguard from clobbering per-post state below:
|
||||||
@@ -118,7 +116,7 @@ export function PostThreadItem({
|
|||||||
overrideBlur={overrideBlur}
|
overrideBlur={overrideBlur}
|
||||||
onPostReply={onPostReply}
|
onPostReply={onPostReply}
|
||||||
hideTopBorder={hideTopBorder}
|
hideTopBorder={hideTopBorder}
|
||||||
postgate={postgate}
|
threadgateRecord={threadgateRecord}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -162,7 +160,7 @@ let PostThreadItemLoaded = ({
|
|||||||
overrideBlur,
|
overrideBlur,
|
||||||
onPostReply,
|
onPostReply,
|
||||||
hideTopBorder,
|
hideTopBorder,
|
||||||
postgate,
|
threadgateRecord,
|
||||||
}: {
|
}: {
|
||||||
post: Shadow<AppBskyFeedDefs.PostView>
|
post: Shadow<AppBskyFeedDefs.PostView>
|
||||||
record: AppBskyFeedPost.Record
|
record: AppBskyFeedPost.Record
|
||||||
@@ -180,7 +178,7 @@ let PostThreadItemLoaded = ({
|
|||||||
overrideBlur: boolean
|
overrideBlur: boolean
|
||||||
onPostReply: (postUri: string | undefined) => void
|
onPostReply: (postUri: string | undefined) => void
|
||||||
hideTopBorder?: boolean
|
hideTopBorder?: boolean
|
||||||
postgate: AppBskyFeedPostgate.Record | undefined
|
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -209,9 +207,6 @@ let PostThreadItemLoaded = ({
|
|||||||
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
|
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
|
||||||
}, [post.uri, post.author])
|
}, [post.uri, post.author])
|
||||||
const repostsTitle = _(msg`Reposts of this post`)
|
const repostsTitle = _(msg`Reposts of this post`)
|
||||||
const {data: threadgateRecord} = useThreadgateRecordQuery({
|
|
||||||
postUri: rootUri,
|
|
||||||
})
|
|
||||||
const isPostHiddenByThreadgate = threadgateRecord?.hiddenReplies?.includes(
|
const isPostHiddenByThreadgate = threadgateRecord?.hiddenReplies?.includes(
|
||||||
post.uri,
|
post.uri,
|
||||||
)
|
)
|
||||||
@@ -370,7 +365,6 @@ let PostThreadItemLoaded = ({
|
|||||||
isThreadAuthor={isThreadAuthor}
|
isThreadAuthor={isThreadAuthor}
|
||||||
translatorUrl={translatorUrl}
|
translatorUrl={translatorUrl}
|
||||||
needsTranslation={needsTranslation}
|
needsTranslation={needsTranslation}
|
||||||
postgate={postgate}
|
|
||||||
/>
|
/>
|
||||||
{post.repostCount !== 0 || post.likeCount !== 0 ? (
|
{post.repostCount !== 0 || post.likeCount !== 0 ? (
|
||||||
// Show this section unless we're *sure* it has no engagement.
|
// Show this section unless we're *sure* it has no engagement.
|
||||||
@@ -421,8 +415,7 @@ let PostThreadItemLoaded = ({
|
|||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
logContext="PostThreadItem"
|
logContext="PostThreadItem"
|
||||||
rootPostUri={rootUri}
|
threadgateRecord={threadgateRecord}
|
||||||
threadgateRecord={threadgateRecord ?? undefined}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -575,8 +568,7 @@ let PostThreadItemLoaded = ({
|
|||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
logContext="PostThreadItem"
|
logContext="PostThreadItem"
|
||||||
rootPostUri={rootUri}
|
threadgateRecord={threadgateRecord}
|
||||||
threadgateRecord={threadgateRecord ?? undefined}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -674,13 +666,11 @@ function ExpandedPostDetails({
|
|||||||
isThreadAuthor,
|
isThreadAuthor,
|
||||||
needsTranslation,
|
needsTranslation,
|
||||||
translatorUrl,
|
translatorUrl,
|
||||||
postgate,
|
|
||||||
}: {
|
}: {
|
||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
isThreadAuthor: boolean
|
isThreadAuthor: boolean
|
||||||
needsTranslation: boolean
|
needsTranslation: boolean
|
||||||
translatorUrl: string
|
translatorUrl: string
|
||||||
postgate: AppBskyFeedPostgate.Record | undefined
|
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -703,17 +693,7 @@ function ExpandedPostDetails({
|
|||||||
]}>
|
]}>
|
||||||
<Text style={[a.text_sm, pal.textLight]}>{niceDate(post.indexedAt)}</Text>
|
<Text style={[a.text_sm, pal.textLight]}>{niceDate(post.indexedAt)}</Text>
|
||||||
{isRootPost && (
|
{isRootPost && (
|
||||||
<WhoCanReply
|
<WhoCanReply post={post} isThreadAuthor={isThreadAuthor} />
|
||||||
post={post}
|
|
||||||
postgate={
|
|
||||||
// TODO maybe define at query
|
|
||||||
postgate ||
|
|
||||||
createPostgateRecord({
|
|
||||||
post: post.uri,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isThreadAuthor={isThreadAuthor}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{needsTranslation && (
|
{needsTranslation && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import * as Clipboard from 'expo-clipboard'
|
|||||||
import {
|
import {
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AppBskyFeedPostgate,
|
|
||||||
AppBskyFeedThreadgate,
|
AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
RichText as RichTextAPI,
|
RichText as RichTextAPI,
|
||||||
@@ -33,17 +32,9 @@ import {
|
|||||||
usePostDeleteMutation,
|
usePostDeleteMutation,
|
||||||
useThreadMuteMutationQueue,
|
useThreadMuteMutationQueue,
|
||||||
} from '#/state/queries/post'
|
} from '#/state/queries/post'
|
||||||
import {
|
import {useToggleQuoteDetachmentMutation} from '#/state/queries/postgate'
|
||||||
usePostgateQuery,
|
import {getMaybeDetachedQuoteEmbed} from '#/state/queries/postgate/util'
|
||||||
useToggleQuoteDetachmentMutation,
|
|
||||||
useWritePostgateMutation,
|
|
||||||
} from '#/state/queries/postgate'
|
|
||||||
import {
|
|
||||||
createPostgateRecord,
|
|
||||||
getMaybeDetachedQuoteEmbed,
|
|
||||||
} from '#/state/queries/postgate/util'
|
|
||||||
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
|
import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
|
||||||
import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate/util'
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {getCurrentRoute} from 'lib/routes/helpers'
|
import {getCurrentRoute} from 'lib/routes/helpers'
|
||||||
import {shareUrl} from 'lib/sharing'
|
import {shareUrl} from 'lib/sharing'
|
||||||
@@ -51,10 +42,9 @@ import {toShareUrl} from 'lib/strings/url-helpers'
|
|||||||
import {useTheme} from 'lib/ThemeContext'
|
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 * as Dialog from '#/components/Dialog'
|
|
||||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
import {EmbedDialog} from '#/components/dialogs/Embed'
|
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 {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
|
||||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
||||||
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
|
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
|
||||||
@@ -91,7 +81,6 @@ let PostDropdownBtn = ({
|
|||||||
hitSlop,
|
hitSlop,
|
||||||
size,
|
size,
|
||||||
timestamp,
|
timestamp,
|
||||||
rootPostUri,
|
|
||||||
threadgateRecord,
|
threadgateRecord,
|
||||||
}: {
|
}: {
|
||||||
testID: string
|
testID: string
|
||||||
@@ -103,7 +92,6 @@ let PostDropdownBtn = ({
|
|||||||
hitSlop?: PressableProps['hitSlop']
|
hitSlop?: PressableProps['hitSlop']
|
||||||
size?: 'lg' | 'md' | 'sm'
|
size?: 'lg' | 'md' | 'sm'
|
||||||
timestamp: string
|
timestamp: string
|
||||||
rootPostUri?: string
|
|
||||||
threadgateRecord?: AppBskyFeedThreadgate.Record
|
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const {hasSession, currentAccount} = useSession()
|
const {hasSession, currentAccount} = useSession()
|
||||||
@@ -282,36 +270,6 @@ let PostDropdownBtn = ({
|
|||||||
[navigation, postUri],
|
[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 () => {
|
const onToggleQuotePostAttachment = React.useCallback(async () => {
|
||||||
if (!quoteEmbed) return
|
if (!quoteEmbed) return
|
||||||
|
|
||||||
@@ -336,10 +294,40 @@ let PostDropdownBtn = ({
|
|||||||
}, [_, quoteEmbed, post, toggleQuoteDetachment])
|
}, [_, quoteEmbed, post, toggleQuoteDetachment])
|
||||||
|
|
||||||
const canEmbed = isWeb && gtMobile && !hideInPWI
|
const canEmbed = isWeb && gtMobile && !hideInPWI
|
||||||
const canHideReply =
|
const canHideReply = !isAuthor && isRootPostAuthor && !isPostHidden && isReply
|
||||||
!isAuthor && isRootPostAuthor && !isPostHidden && rootPostUri && isReply
|
|
||||||
const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer
|
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 (
|
return (
|
||||||
<EventStopper onKeyDown={false}>
|
<EventStopper onKeyDown={false}>
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
@@ -643,114 +631,15 @@ let PostDropdownBtn = ({
|
|||||||
onSelectChat={onSelectChatToShareTo}
|
onSelectChat={onSelectChatToShareTo}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PostInteractionSettingsDialogControlled
|
<PostInteractionSettingsDialog
|
||||||
control={postInteractionSettingsDialogControl}
|
control={postInteractionSettingsDialogControl}
|
||||||
postUri={post.uri}
|
postUri={post.uri}
|
||||||
threadgateRecord={threadgateRecord}
|
rootPostUri={rootUri}
|
||||||
replySettingsDisabled={!isRootPostAuthor}
|
initialThreadgateView={post.threadgate}
|
||||||
/>
|
/>
|
||||||
</EventStopper>
|
</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)
|
PostDropdownBtn = memo(PostDropdownBtn)
|
||||||
export {PostDropdownBtn}
|
export {PostDropdownBtn}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ let PostCtrls = ({
|
|||||||
style,
|
style,
|
||||||
onPressReply,
|
onPressReply,
|
||||||
logContext,
|
logContext,
|
||||||
rootPostUri,
|
|
||||||
threadgateRecord,
|
threadgateRecord,
|
||||||
}: {
|
}: {
|
||||||
big?: boolean
|
big?: boolean
|
||||||
@@ -71,7 +70,6 @@ let PostCtrls = ({
|
|||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
onPressReply: () => void
|
onPressReply: () => void
|
||||||
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
rootPostUri?: string
|
|
||||||
threadgateRecord?: AppBskyFeedThreadgate.Record
|
threadgateRecord?: AppBskyFeedThreadgate.Record
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -344,7 +342,6 @@ let PostCtrls = ({
|
|||||||
style={{padding: 5}}
|
style={{padding: 5}}
|
||||||
hitSlop={POST_CTRL_HITSLOP}
|
hitSlop={POST_CTRL_HITSLOP}
|
||||||
timestamp={post.indexedAt}
|
timestamp={post.indexedAt}
|
||||||
rootPostUri={rootPostUri}
|
|
||||||
threadgateRecord={threadgateRecord}
|
threadgateRecord={threadgateRecord}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user