Files
bsky-social-app/src/view/com/threadgate/WhoCanReply.tsx
T
Paul Frazee d6ce16d15a Implement thread locking (#4545)
* Add the ability to edit threadgates

* Fix bottom border on mobile

* Refresh thread after threadgate edit
2024-06-18 12:07:56 -07:00

230 lines
6.0 KiB
TypeScript

import React from 'react'
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
import {AppBskyFeedDefs, AppBskyGraphDefs, AtUri} 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 {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,
})
}
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) => (
<>
<Rule
key={`rule-${i}`}
rule={rule}
post={post}
lists={post.threadgate!.lists}
/>
<Separator key={`sep-${i}`} i={i} length={settings.length} />
</>
))}{' '}
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 <>, </>
}