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 {
|
||||
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()
|
||||
|
||||
@@ -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()
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 && (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user