264 lines
6.9 KiB
TypeScript
264 lines
6.9 KiB
TypeScript
import React from 'react'
|
|
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
|
|
import {
|
|
AppBskyFeedDefs,
|
|
AppBskyFeedGetPostThread,
|
|
AppBskyGraphDefs,
|
|
AtUri,
|
|
BskyAgent,
|
|
} from '@atproto/api'
|
|
import {msg, Trans} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {useQueryClient} from '@tanstack/react-query'
|
|
|
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
|
import {createThreadgate} from '#/lib/api'
|
|
import {until} from '#/lib/async/until'
|
|
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
|
import {usePalette} from '#/lib/hooks/usePalette'
|
|
import {makeListLink, makeProfileLink} from '#/lib/routes/links'
|
|
import {colors} from '#/lib/styles'
|
|
import {logger} from '#/logger'
|
|
import {isNative} from '#/platform/detection'
|
|
import {useModalControls} from '#/state/modals'
|
|
import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread'
|
|
import {
|
|
ThreadgateSetting,
|
|
threadgateViewToSettings,
|
|
} from '#/state/queries/threadgate'
|
|
import {useAgent} from '#/state/session'
|
|
import * as Toast from 'view/com/util/Toast'
|
|
import {Button} from '#/components/Button'
|
|
import {TextLink} from '../util/Link'
|
|
import {Text} from '../util/text/Text'
|
|
|
|
export function WhoCanReply({
|
|
post,
|
|
isThreadAuthor,
|
|
style,
|
|
}: {
|
|
post: AppBskyFeedDefs.PostView
|
|
isThreadAuthor: boolean
|
|
style?: StyleProp<ViewStyle>
|
|
}) {
|
|
const {track} = useAnalytics()
|
|
const {_} = useLingui()
|
|
const pal = usePalette('default')
|
|
const agent = useAgent()
|
|
const queryClient = useQueryClient()
|
|
const {openModal} = useModalControls()
|
|
const containerStyles = useColorSchemeStyle(
|
|
{
|
|
backgroundColor: pal.colors.unreadNotifBg,
|
|
},
|
|
{
|
|
backgroundColor: pal.colors.unreadNotifBg,
|
|
},
|
|
)
|
|
const textStyles = useColorSchemeStyle(
|
|
{color: colors.blue5},
|
|
{color: colors.blue1},
|
|
)
|
|
const hoverStyles = useColorSchemeStyle(
|
|
{
|
|
backgroundColor: colors.white,
|
|
},
|
|
{
|
|
backgroundColor: pal.colors.background,
|
|
},
|
|
)
|
|
const settings = React.useMemo(
|
|
() => threadgateViewToSettings(post.threadgate),
|
|
[post],
|
|
)
|
|
const isRootPost = !('reply' in post.record)
|
|
|
|
const onPressEdit = () => {
|
|
track('Post:EditThreadgateOpened')
|
|
if (isNative && Keyboard.isVisible()) {
|
|
Keyboard.dismiss()
|
|
}
|
|
openModal({
|
|
name: 'threadgate',
|
|
settings,
|
|
async onConfirm(newSettings: ThreadgateSetting[]) {
|
|
try {
|
|
if (newSettings.length) {
|
|
await createThreadgate(agent, post.uri, newSettings)
|
|
} else {
|
|
await agent.api.com.atproto.repo.deleteRecord({
|
|
repo: agent.session!.did,
|
|
collection: 'app.bsky.feed.threadgate',
|
|
rkey: new AtUri(post.uri).rkey,
|
|
})
|
|
}
|
|
await whenAppViewReady(agent, post.uri, res => {
|
|
const thread = res.data.thread
|
|
if (AppBskyFeedDefs.isThreadViewPost(thread)) {
|
|
const fetchedSettings = threadgateViewToSettings(
|
|
thread.post.threadgate,
|
|
)
|
|
return (
|
|
JSON.stringify(fetchedSettings) === JSON.stringify(newSettings)
|
|
)
|
|
}
|
|
return false
|
|
})
|
|
Toast.show('Thread settings updated')
|
|
queryClient.invalidateQueries({
|
|
queryKey: [POST_THREAD_RQKEY_ROOT],
|
|
})
|
|
track('Post:ThreadgateEdited')
|
|
} catch (err) {
|
|
Toast.show(
|
|
'There was an issue. Please check your internet connection and try again.',
|
|
)
|
|
logger.error('Failed to edit threadgate', {message: err})
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
if (!isRootPost) {
|
|
return null
|
|
}
|
|
if (!settings.length && !isThreadAuthor) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<View
|
|
style={[
|
|
{
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
paddingLeft: 18,
|
|
paddingRight: 14,
|
|
paddingVertical: 10,
|
|
borderTopWidth: 1,
|
|
},
|
|
pal.border,
|
|
containerStyles,
|
|
style,
|
|
]}>
|
|
<View style={{flex: 1, paddingVertical: 6}}>
|
|
<Text type="sm" style={[{flexWrap: 'wrap'}, textStyles]}>
|
|
{!settings.length ? (
|
|
<Trans>Everybody can reply.</Trans>
|
|
) : settings[0].type === 'nobody' ? (
|
|
<Trans>Replies to this thread are disabled.</Trans>
|
|
) : (
|
|
<Trans>
|
|
Only{' '}
|
|
{settings.map((rule, i) => (
|
|
<React.Fragment key={`rule-${i}`}>
|
|
<Rule
|
|
rule={rule}
|
|
post={post}
|
|
lists={post.threadgate!.lists}
|
|
/>
|
|
<Separator key={`sep-${i}`} i={i} length={settings.length} />
|
|
</React.Fragment>
|
|
))}{' '}
|
|
can reply.
|
|
</Trans>
|
|
)}
|
|
</Text>
|
|
</View>
|
|
{isThreadAuthor && (
|
|
<View>
|
|
<Button label={_(msg`Edit`)} onPress={onPressEdit}>
|
|
{({hovered}) => (
|
|
<View
|
|
style={[
|
|
hovered && hoverStyles,
|
|
{paddingVertical: 6, paddingHorizontal: 8, borderRadius: 8},
|
|
]}>
|
|
<Text type="sm" style={pal.link}>
|
|
<Trans>Edit</Trans>
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</Button>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
function Rule({
|
|
rule,
|
|
post,
|
|
lists,
|
|
}: {
|
|
rule: ThreadgateSetting
|
|
post: AppBskyFeedDefs.PostView
|
|
lists: AppBskyGraphDefs.ListViewBasic[] | undefined
|
|
}) {
|
|
const pal = usePalette('default')
|
|
if (rule.type === 'mention') {
|
|
return <Trans>mentioned users</Trans>
|
|
}
|
|
if (rule.type === 'following') {
|
|
return (
|
|
<Trans>
|
|
users followed by{' '}
|
|
<TextLink
|
|
type="sm"
|
|
href={makeProfileLink(post.author)}
|
|
text={`@${post.author.handle}`}
|
|
style={pal.link}
|
|
/>
|
|
</Trans>
|
|
)
|
|
}
|
|
if (rule.type === 'list') {
|
|
const list = lists?.find(l => l.uri === rule.list)
|
|
if (list) {
|
|
const listUrip = new AtUri(list.uri)
|
|
return (
|
|
<Trans>
|
|
<TextLink
|
|
type="sm"
|
|
href={makeListLink(listUrip.hostname, listUrip.rkey)}
|
|
text={list.name}
|
|
style={pal.link}
|
|
/>{' '}
|
|
members
|
|
</Trans>
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
function Separator({i, length}: {i: number; length: number}) {
|
|
if (length < 2 || i === length - 1) {
|
|
return null
|
|
}
|
|
if (i === length - 2) {
|
|
return (
|
|
<>
|
|
{length > 2 ? ',' : ''} <Trans>and</Trans>{' '}
|
|
</>
|
|
)
|
|
}
|
|
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,
|
|
}),
|
|
)
|
|
}
|