mirror of https://github.com/tooot-app/app
Fix #613
This commit is contained in:
parent
e8eb62e2d0
commit
653b588c29
|
@ -0,0 +1,114 @@
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import { Fragment } from 'react'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { View } from 'react-native'
|
||||||
|
import { TouchableNativeFeedback } from 'react-native-gesture-handler'
|
||||||
|
import Icon from './Icon'
|
||||||
|
import CustomText from './Text'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
onPress: () => void
|
||||||
|
filter: Mastodon.Filter<'v2'>
|
||||||
|
button?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Filter: React.FC<Props> = ({ onPress, filter, button }) => {
|
||||||
|
const { t } = useTranslation(['common', 'screenTabs'])
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableNativeFeedback onPress={onPress}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
paddingVertical: StyleConstants.Spacing.S,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.backgroundDefault
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='M'
|
||||||
|
children={filter.title}
|
||||||
|
style={{ color: colors.primaryDefault }}
|
||||||
|
numberOfLines={1}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: StyleConstants.Spacing.XS
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filter.expires_at && new Date() > new Date(filter.expires_at) ? (
|
||||||
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
fontWeight='Bold'
|
||||||
|
children={t('screenTabs:me.preferencesFilters.expired')}
|
||||||
|
style={{ color: colors.red, marginRight: StyleConstants.Spacing.M }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{filter.keywords?.length ? (
|
||||||
|
<CustomText
|
||||||
|
children={t('screenTabs:me.preferencesFilters.keywords', {
|
||||||
|
count: filter.keywords.length
|
||||||
|
})}
|
||||||
|
style={{ color: colors.primaryDefault }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{filter.keywords?.length && filter.statuses?.length ? (
|
||||||
|
<CustomText
|
||||||
|
children={t('common:separator')}
|
||||||
|
style={{ color: colors.primaryDefault }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{filter.statuses?.length ? (
|
||||||
|
<CustomText
|
||||||
|
children={t('screenTabs:me.preferencesFilters.statuses', {
|
||||||
|
count: filter.statuses.length
|
||||||
|
})}
|
||||||
|
style={{ color: colors.primaryDefault }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
<CustomText
|
||||||
|
style={{ color: colors.secondary }}
|
||||||
|
children={
|
||||||
|
<Trans
|
||||||
|
ns='screenTabs'
|
||||||
|
i18nKey='me.preferencesFilters.context'
|
||||||
|
components={[
|
||||||
|
<>
|
||||||
|
{filter.context.map((c, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<CustomText
|
||||||
|
style={{
|
||||||
|
color: colors.secondary,
|
||||||
|
textDecorationColor: colors.disabled,
|
||||||
|
textDecorationLine: 'underline',
|
||||||
|
textDecorationStyle: 'solid'
|
||||||
|
}}
|
||||||
|
children={t(`screenTabs:me.preferencesFilters.contexts.${c}`)}
|
||||||
|
/>
|
||||||
|
<CustomText children={t('common:separator')} />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{button || (
|
||||||
|
<Icon
|
||||||
|
name='chevron-right'
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
color={colors.primaryDefault}
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableNativeFeedback>
|
||||||
|
)
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ const ComponentHashtag: React.FC<PropsWithChildren & Props> = ({
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
navigation.push('Tab-Shared-Hashtag', { hashtag: hashtag.name })
|
navigation.push('Tab-Shared-Hashtag', { tag_name: hashtag.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
const padding = StyleConstants.Spacing.Global.PagePadding
|
const padding = StyleConstants.Spacing.Global.PagePadding
|
||||||
|
|
|
@ -69,7 +69,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||||
|
|
||||||
const [followedTags] = useAccountStorage.object('followed_tags')
|
const [followedTags] = useAccountStorage.object('followed_tags')
|
||||||
|
|
||||||
const MAX_ALLOWED_LINES = 30
|
const MAX_ALLOWED_LINES = 35
|
||||||
const [totalLines, setTotalLines] = useState<number>()
|
const [totalLines, setTotalLines] = useState<number>()
|
||||||
const [expanded, setExpanded] = useState(highlighted)
|
const [expanded, setExpanded] = useState(highlighted)
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||||
tag?.length &&
|
tag?.length &&
|
||||||
!disableDetails &&
|
!disableDetails &&
|
||||||
!sameHashtag &&
|
!sameHashtag &&
|
||||||
navigation.push('Tab-Shared-Hashtag', { hashtag: tag })
|
navigation.push('Tab-Shared-Hashtag', { tag_name: tag })
|
||||||
}
|
}
|
||||||
children={children}
|
children={children}
|
||||||
/>
|
/>
|
||||||
|
@ -203,9 +203,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!disableDetails) {
|
if (!disableDetails) {
|
||||||
if (shouldBeTag) {
|
if (shouldBeTag) {
|
||||||
navigation.push('Tab-Shared-Hashtag', {
|
navigation.push('Tab-Shared-Hashtag', { tag_name: content.substring(1) })
|
||||||
hashtag: content.substring(1)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
await openLink(href, navigation)
|
await openLink(href, navigation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,8 +137,8 @@ const TimelinePoll: React.FC = () => {
|
||||||
marginRight: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
name={
|
name={
|
||||||
`${poll.own_votes?.includes(index) ? 'Check' : ''}${
|
`${poll.own_votes?.includes(index) ? 'check-' : ''}${
|
||||||
poll.multiple ? 'Square' : 'Circle'
|
poll.multiple ? 'square' : 'circle'
|
||||||
}` as any
|
}` as any
|
||||||
}
|
}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import haptics from '@components/haptics'
|
||||||
|
import { displayMessage } from '@components/Message'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||||
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||||
|
import { TabSharedStackParamList } from '@utils/navigation/navigators'
|
||||||
|
import { QueryKeyFollowedTags, useTagMutation, useTagQuery } from '@utils/queryHooks/tags'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const menuHashtag = ({
|
||||||
|
tag_name,
|
||||||
|
queryKey
|
||||||
|
}: {
|
||||||
|
tag_name: Mastodon.Tag['name']
|
||||||
|
queryKey?: QueryKeyTimeline
|
||||||
|
}): ContextMenu => {
|
||||||
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<TabSharedStackParamList, any, undefined>>()
|
||||||
|
const { t } = useTranslation(['common', 'componentContextMenu'])
|
||||||
|
|
||||||
|
const canFollowTags = featureCheck('follow_tags')
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isFetching: tagIsFetching,
|
||||||
|
refetch: tagRefetch
|
||||||
|
} = useTagQuery({ tag_name, options: { enabled: canFollowTags } })
|
||||||
|
const tagMutation = useTagMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
haptics('Light')
|
||||||
|
tagRefetch()
|
||||||
|
const queryKeyFollowedTags: QueryKeyFollowedTags = ['FollowedTags']
|
||||||
|
queryClient.invalidateQueries({ queryKey: queryKeyFollowedTags })
|
||||||
|
},
|
||||||
|
onError: (err: any, { to }) => {
|
||||||
|
displayMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common:message.error.message', {
|
||||||
|
function: t('componentContextMenu:hashtag.follow', {
|
||||||
|
defaultValue: 'false',
|
||||||
|
context: to.toString()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
...(err.status &&
|
||||||
|
typeof err.status === 'number' &&
|
||||||
|
err.data &&
|
||||||
|
err.data.error &&
|
||||||
|
typeof err.data.error === 'string' && {
|
||||||
|
description: err.data.error
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const menus: ContextMenu = []
|
||||||
|
|
||||||
|
menus.push([
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
key: 'hashtag-follow',
|
||||||
|
props: {
|
||||||
|
onSelect: () =>
|
||||||
|
typeof data?.following === 'boolean' &&
|
||||||
|
tagMutation.mutate({ tag_name, to: !data.following }),
|
||||||
|
disabled: tagIsFetching,
|
||||||
|
destructive: false,
|
||||||
|
hidden: !canFollowTags
|
||||||
|
},
|
||||||
|
title: t('componentContextMenu:hashtag.follow.action', {
|
||||||
|
defaultValue: 'false',
|
||||||
|
context: (data?.following || false).toString()
|
||||||
|
}),
|
||||||
|
icon: data?.following ? 'rectangle.stack.badge.minus' : 'rectangle.stack.badge.plus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
key: 'hashtag-filter',
|
||||||
|
props: {
|
||||||
|
onSelect: () =>
|
||||||
|
navigation.navigate('Tab-Shared-Filter', { source: 'hashtag', tag_name, queryKey }),
|
||||||
|
disabled: false,
|
||||||
|
destructive: false,
|
||||||
|
hidden: !canFollowTags
|
||||||
|
},
|
||||||
|
title: t('componentContextMenu:hashtag.filter.action'),
|
||||||
|
icon: 'line.3.horizontal.decrease'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
return menus
|
||||||
|
}
|
||||||
|
|
||||||
|
export default menuHashtag
|
|
@ -231,6 +231,22 @@ const menuStatus = ({
|
||||||
context: (status.pinned || false).toString()
|
context: (status.pinned || false).toString()
|
||||||
}),
|
}),
|
||||||
icon: status.pinned ? 'pin.slash' : 'pin'
|
icon: status.pinned ? 'pin.slash' : 'pin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
key: 'status-filter',
|
||||||
|
props: {
|
||||||
|
// @ts-ignore
|
||||||
|
onSelect: () => navigation.navigate('Tab-Shared-Filter', { source: 'status', status, queryKey }),
|
||||||
|
disabled: false,
|
||||||
|
destructive: false,
|
||||||
|
hidden: !('filtered' in status)
|
||||||
|
},
|
||||||
|
title: t('componentContextMenu:status.filter.action', {
|
||||||
|
defaultValue: 'false',
|
||||||
|
context: (!!status.filtered?.length).toString()
|
||||||
|
}),
|
||||||
|
icon: status.pinned ? 'rectangle.badge.checkmark' : 'rectangle.badge.xmark'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"action_false": "Follow user",
|
"action_false": "Follow user",
|
||||||
"action_true": "Unfollow user"
|
"action_true": "Unfollow user"
|
||||||
},
|
},
|
||||||
"inLists": "Lists containing user",
|
"inLists": "Lists containing user ...",
|
||||||
"showBoosts": {
|
"showBoosts": {
|
||||||
"action_false": "Show user's boosts",
|
"action_false": "Show user's boosts",
|
||||||
"action_true": "Hide users's boosts"
|
"action_true": "Hide users's boosts"
|
||||||
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Unmute user"
|
"action_true": "Unmute user"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "Follow as...",
|
"trigger": "Follow as ...",
|
||||||
"succeed_default": "Now following @{{target}} with @{{source}}",
|
"succeed_default": "Now following @{{target}} with @{{source}}",
|
||||||
"succeed_locked": "Sent follow request to @{{target}} with {{source}}, pending approval",
|
"succeed_locked": "Sent follow request to @{{target}} with {{source}}, pending approval",
|
||||||
"failed": "Follow as"
|
"failed": "Follow as"
|
||||||
},
|
},
|
||||||
"blockReport": "Block and report...",
|
"blockReport": "Block and report",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Block user",
|
"action_false": "Block user",
|
||||||
"action_true": "Unblock user",
|
"action_true": "Unblock user",
|
||||||
|
@ -54,6 +54,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hashtag": {
|
||||||
|
"follow": {
|
||||||
|
"action_false": "Follow",
|
||||||
|
"action_true": "Unfollow",
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"action": "Filter hashtag ..."
|
||||||
|
}
|
||||||
|
},
|
||||||
"share": {
|
"share": {
|
||||||
"status": {
|
"status": {
|
||||||
"action": "Share toot"
|
"action": "Share toot"
|
||||||
|
@ -88,6 +97,10 @@
|
||||||
"pin": {
|
"pin": {
|
||||||
"action_false": "Pin toot",
|
"action_false": "Pin toot",
|
||||||
"action_true": "Unpin toot"
|
"action_true": "Unpin toot"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"action_false": "Filter toot ...",
|
||||||
|
"action_true": "Manage filters ..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -399,9 +399,9 @@
|
||||||
"attachments": {
|
"attachments": {
|
||||||
"name": "<0 /><1>'s media</1>"
|
"name": "<0 /><1>'s media</1>"
|
||||||
},
|
},
|
||||||
"hashtag": {
|
"filter": {
|
||||||
"follow": "Follow",
|
"name": "Add to filter",
|
||||||
"unfollow": "Unfollow"
|
"existed": "Existed in these filters"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"name": "Edit History"
|
"name": "Edit History"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import ComponentHashtag from '@components/Hashtag'
|
||||||
import { displayMessage } from '@components/Message'
|
import { displayMessage } from '@components/Message'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { useFollowedTagsQuery, useTagsMutation } from '@utils/queryHooks/tags'
|
import { useFollowedTagsQuery, useTagMutation } from '@utils/queryHooks/tags'
|
||||||
import { flattenPages } from '@utils/queryHooks/utils'
|
import { flattenPages } from '@utils/queryHooks/utils'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
@ -13,7 +13,7 @@ import { FlatList } from 'react-native-gesture-handler'
|
||||||
const TabMeFollowedTags: React.FC<TabMeStackScreenProps<'Tab-Me-FollowedTags'>> = ({
|
const TabMeFollowedTags: React.FC<TabMeStackScreenProps<'Tab-Me-FollowedTags'>> = ({
|
||||||
navigation
|
navigation
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation(['common', 'screenTabs'])
|
const { t } = useTranslation(['common', 'screenTabs', 'componentContextMenu'])
|
||||||
|
|
||||||
const { data, fetchNextPage, refetch } = useFollowedTagsQuery()
|
const { data, fetchNextPage, refetch } = useFollowedTagsQuery()
|
||||||
const flattenData = flattenPages(data)
|
const flattenData = flattenPages(data)
|
||||||
|
@ -24,7 +24,7 @@ const TabMeFollowedTags: React.FC<TabMeStackScreenProps<'Tab-Me-FollowedTags'>>
|
||||||
}
|
}
|
||||||
}, [flattenData.length])
|
}, [flattenData.length])
|
||||||
|
|
||||||
const mutation = useTagsMutation({
|
const mutation = useTagMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
refetch()
|
refetch()
|
||||||
|
@ -33,9 +33,10 @@ const TabMeFollowedTags: React.FC<TabMeStackScreenProps<'Tab-Me-FollowedTags'>>
|
||||||
displayMessage({
|
displayMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:message.error.message', {
|
message: t('common:message.error.message', {
|
||||||
function: to
|
function: t('componentContextMenu:hashtag.follow.action', {
|
||||||
? t('screenTabs:shared.hashtag.follow')
|
defaultValue: 'false',
|
||||||
: t('screenTabs:shared.hashtag.unfollow')
|
context: to.toString()
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
|
@ -58,8 +59,11 @@ const TabMeFollowedTags: React.FC<TabMeStackScreenProps<'Tab-Me-FollowedTags'>>
|
||||||
children={
|
children={
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content={t('screenTabs:shared.hashtag.unfollow')}
|
content={t('componentContextMenu:hashtag.follow.action', {
|
||||||
onPress={() => mutation.mutate({ tag: item.name, to: !item.following })}
|
defaultValue: 'fase',
|
||||||
|
context: 'false'
|
||||||
|
})}
|
||||||
|
onPress={() => mutation.mutate({ tag_name: item.name, to: !item.following })}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
import { Filter } from '@components/Filter'
|
||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import CustomText from '@components/Text'
|
|
||||||
import apiInstance from '@utils/api/instance'
|
import apiInstance from '@utils/api/instance'
|
||||||
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
|
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { useFiltersQuery } from '@utils/queryHooks/filters'
|
import { useFiltersQuery } from '@utils/queryHooks/filters'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { Fragment, useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, TouchableNativeFeedback, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { SwipeListView } from 'react-native-swipe-list-view'
|
import { SwipeListView } from 'react-native-swipe-list-view'
|
||||||
|
|
||||||
const TabMePreferencesFilters: React.FC<
|
const TabMePreferencesFilters: React.FC<
|
||||||
|
@ -39,6 +39,7 @@ const TabMePreferencesFilters: React.FC<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SwipeListView
|
<SwipeListView
|
||||||
|
contentContainerStyle={{ padding: StyleConstants.Spacing.Global.PagePadding }}
|
||||||
renderHiddenItem={({ item }) => (
|
renderHiddenItem={({ item }) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
|
@ -65,98 +66,10 @@ const TabMePreferencesFilters: React.FC<
|
||||||
filter.expires_at ? new Date().getTime() - new Date(filter.expires_at).getTime() : 1
|
filter.expires_at ? new Date().getTime() - new Date(filter.expires_at).getTime() : 1
|
||||||
)}
|
)}
|
||||||
renderItem={({ item: filter }) => (
|
renderItem={({ item: filter }) => (
|
||||||
<TouchableNativeFeedback
|
<Filter
|
||||||
|
filter={filter}
|
||||||
onPress={() => navigation.navigate('Tab-Me-Preferences-Filter', { type: 'edit', filter })}
|
onPress={() => navigation.navigate('Tab-Me-Preferences-Filter', { type: 'edit', filter })}
|
||||||
>
|
/>
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: colors.backgroundDefault
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<CustomText
|
|
||||||
fontStyle='M'
|
|
||||||
children={filter.title}
|
|
||||||
style={{ color: colors.primaryDefault }}
|
|
||||||
numberOfLines={1}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: StyleConstants.Spacing.XS
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{filter.expires_at && new Date() > new Date(filter.expires_at) ? (
|
|
||||||
<CustomText
|
|
||||||
fontStyle='S'
|
|
||||||
fontWeight='Bold'
|
|
||||||
children={t('screenTabs:me.preferencesFilters.expired')}
|
|
||||||
style={{ color: colors.red, marginRight: StyleConstants.Spacing.M }}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{filter.keywords?.length ? (
|
|
||||||
<CustomText
|
|
||||||
children={t('screenTabs:me.preferencesFilters.keywords', {
|
|
||||||
count: filter.keywords.length
|
|
||||||
})}
|
|
||||||
style={{ color: colors.primaryDefault }}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{filter.keywords?.length && filter.statuses?.length ? (
|
|
||||||
<CustomText
|
|
||||||
children={t('common:separator')}
|
|
||||||
style={{ color: colors.primaryDefault }}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{filter.statuses?.length ? (
|
|
||||||
<CustomText
|
|
||||||
children={t('screenTabs:me.preferencesFilters.statuses', {
|
|
||||||
count: filter.statuses.length
|
|
||||||
})}
|
|
||||||
style={{ color: colors.primaryDefault }}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
<CustomText
|
|
||||||
style={{ color: colors.secondary }}
|
|
||||||
children={
|
|
||||||
<Trans
|
|
||||||
ns='screenTabs'
|
|
||||||
i18nKey='me.preferencesFilters.context'
|
|
||||||
components={[
|
|
||||||
<>
|
|
||||||
{filter.context.map((c, index) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<CustomText
|
|
||||||
style={{
|
|
||||||
color: colors.secondary,
|
|
||||||
textDecorationColor: colors.disabled,
|
|
||||||
textDecorationLine: 'underline',
|
|
||||||
textDecorationStyle: 'solid'
|
|
||||||
}}
|
|
||||||
children={t(`screenTabs:me.preferencesFilters.contexts.${c}`)}
|
|
||||||
/>
|
|
||||||
<CustomText children={t('common:separator')} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Icon
|
|
||||||
name='chevron-right'
|
|
||||||
size={StyleConstants.Font.Size.L}
|
|
||||||
color={colors.primaryDefault}
|
|
||||||
style={{ marginLeft: 8 }}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</TouchableNativeFeedback>
|
|
||||||
)}
|
)}
|
||||||
ItemSeparatorComponent={ComponentSeparator}
|
ItemSeparatorComponent={ComponentSeparator}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -27,15 +27,13 @@ const TabSharedAccountInLists: React.FC<
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
presentation: 'modal',
|
presentation: 'modal',
|
||||||
title: t('screenTabs:shared.accountInLists.name', { username: account.username }),
|
title: t('screenTabs:shared.accountInLists.name', { username: account.username }),
|
||||||
headerRight: () => {
|
headerRight: () => (
|
||||||
return (
|
<HeaderRight
|
||||||
<HeaderRight
|
type='text'
|
||||||
type='text'
|
content={t('common:buttons.done')}
|
||||||
content={t('common:buttons.done')}
|
onPress={() => navigation.pop(1)}
|
||||||
onPress={() => navigation.pop(1)}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -66,11 +64,11 @@ const TabSharedAccountInLists: React.FC<
|
||||||
<SectionList
|
<SectionList
|
||||||
style={{ padding: StyleConstants.Spacing.Global.PagePadding }}
|
style={{ padding: StyleConstants.Spacing.Global.PagePadding }}
|
||||||
sections={sections}
|
sections={sections}
|
||||||
SectionSeparatorComponent={props => {
|
SectionSeparatorComponent={props =>
|
||||||
return props.leadingItem && props.trailingSection ? (
|
props.leadingItem && props.trailingSection ? (
|
||||||
<View style={{ flex: 1, height: StyleConstants.Spacing.Global.PagePadding * 2 }} />
|
<View style={{ height: StyleConstants.Spacing.XL }} />
|
||||||
) : null
|
) : null
|
||||||
}}
|
}
|
||||||
renderSectionHeader={({ section: { title, data } }) =>
|
renderSectionHeader={({ section: { title, data } }) =>
|
||||||
data.length ? (
|
data.length ? (
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }} children={title} />
|
<CustomText fontStyle='S' style={{ color: colors.secondary }} children={title} />
|
||||||
|
@ -117,7 +115,7 @@ const TabSharedAccountInLists: React.FC<
|
||||||
title={item.title}
|
title={item.title}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
></SectionList>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
import Button from '@components/Button'
|
||||||
|
import { Filter } from '@components/Filter'
|
||||||
|
import { HeaderRight } from '@components/Header'
|
||||||
|
import Hr from '@components/Hr'
|
||||||
|
import CustomText from '@components/Text'
|
||||||
|
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||||
|
import { TabSharedStackScreenProps, useNavState } from '@utils/navigation/navigators'
|
||||||
|
import { useFilterMutation, useFiltersQuery } from '@utils/queryHooks/filters'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { SectionList, View } from 'react-native'
|
||||||
|
|
||||||
|
const TabSharedFilter: React.FC<TabSharedStackScreenProps<'Tab-Shared-Filter'>> = ({
|
||||||
|
navigation,
|
||||||
|
route: { params }
|
||||||
|
}) => {
|
||||||
|
if (!featureCheck('filter_server_side')) {
|
||||||
|
navigation.goBack()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { colors } = useTheme()
|
||||||
|
const { t } = useTranslation(['common', 'screenTabs'])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
title: t('screenTabs:shared.filter.name'),
|
||||||
|
headerRight: () => (
|
||||||
|
<HeaderRight
|
||||||
|
type='text'
|
||||||
|
content={t('common:buttons.done')}
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { data, isFetching, refetch } = useFiltersQuery<'v2'>({ version: 'v2' })
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
id: 'add',
|
||||||
|
data:
|
||||||
|
data?.filter(filter => {
|
||||||
|
switch (params.source) {
|
||||||
|
case 'hashtag':
|
||||||
|
return !filter.keywords.find(keyword => keyword.keyword === `#${params.tag_name}`)
|
||||||
|
case 'status':
|
||||||
|
return !filter.statuses.find(({ status_id }) => status_id === params.status.id)
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'remove',
|
||||||
|
title: t('screenTabs:shared.filter.existed'),
|
||||||
|
data:
|
||||||
|
data?.filter(filter => {
|
||||||
|
switch (params.source) {
|
||||||
|
case 'hashtag':
|
||||||
|
return !!filter.keywords.find(keyword => keyword.keyword === `#${params.tag_name}`)
|
||||||
|
case 'status':
|
||||||
|
return !!filter.statuses.find(({ status_id }) => status_id === params.status.id)
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const mutation = useFilterMutation()
|
||||||
|
|
||||||
|
const navState = useNavState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionList
|
||||||
|
contentContainerStyle={{ padding: StyleConstants.Spacing.Global.PagePadding }}
|
||||||
|
sections={sections}
|
||||||
|
SectionSeparatorComponent={props =>
|
||||||
|
props.leadingItem && props.trailingSection ? (
|
||||||
|
<View style={{ height: StyleConstants.Spacing.XL }} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
renderSectionHeader={({ section: { title, data } }) =>
|
||||||
|
title && data.length ? (
|
||||||
|
<CustomText fontStyle='S' style={{ color: colors.secondary }} children={title} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
ItemSeparatorComponent={Hr}
|
||||||
|
renderItem={({ item, section }) => (
|
||||||
|
<Filter
|
||||||
|
filter={item}
|
||||||
|
button={
|
||||||
|
<Button
|
||||||
|
type='icon'
|
||||||
|
content={section.id === 'add' ? 'plus' : 'minus'}
|
||||||
|
round
|
||||||
|
disabled={isFetching}
|
||||||
|
onPress={() => {
|
||||||
|
if (section.id === 'add' || section.id === 'remove') {
|
||||||
|
switch (params.source) {
|
||||||
|
case 'status':
|
||||||
|
mutation
|
||||||
|
.mutateAsync({
|
||||||
|
source: 'status',
|
||||||
|
filter: item,
|
||||||
|
action: section.id,
|
||||||
|
status: params.status
|
||||||
|
})
|
||||||
|
.then(() => refetch())
|
||||||
|
break
|
||||||
|
case 'hashtag':
|
||||||
|
mutation
|
||||||
|
.mutateAsync({
|
||||||
|
source: 'keyword',
|
||||||
|
filter: item,
|
||||||
|
action: section.id,
|
||||||
|
keyword: `#${params.tag_name}`
|
||||||
|
})
|
||||||
|
.then(() => refetch())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onPress={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabSharedFilter
|
|
@ -1,85 +1,69 @@
|
||||||
import haptics from '@components/haptics'
|
import menuHashtag from '@components/contextMenu/hashtag'
|
||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import { displayMessage } from '@components/Message'
|
|
||||||
import Timeline from '@components/Timeline'
|
import Timeline from '@components/Timeline'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
|
||||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyFollowedTags, useTagsMutation, useTagsQuery } from '@utils/queryHooks/tags'
|
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import React, { useEffect } from 'react'
|
import React, { Fragment, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||||
|
|
||||||
const TabSharedHashtag: React.FC<TabSharedStackScreenProps<'Tab-Shared-Hashtag'>> = ({
|
const TabSharedHashtag: React.FC<TabSharedStackScreenProps<'Tab-Shared-Hashtag'>> = ({
|
||||||
navigation,
|
navigation,
|
||||||
route: {
|
route: {
|
||||||
params: { hashtag }
|
params: { tag_name }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Hashtag', hashtag }]
|
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Hashtag', tag_name }]
|
||||||
|
|
||||||
|
const canFollowTags = featureCheck('follow_tags')
|
||||||
|
const canFilterTag = featureCheck('filter_server_side')
|
||||||
|
const mHashtag = menuHashtag({ tag_name, queryKey })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />,
|
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />,
|
||||||
title: `#${decodeURIComponent(hashtag)}`
|
title: `#${decodeURIComponent(tag_name)}`
|
||||||
})
|
})
|
||||||
navigation.setParams({ queryKey: queryKey })
|
navigation.setParams({ queryKey: queryKey })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const { t } = useTranslation(['common', 'screenTabs'])
|
|
||||||
|
|
||||||
const canFollowTags = featureCheck('follow_tags')
|
|
||||||
const { data, isFetching, refetch } = useTagsQuery({
|
|
||||||
tag: hashtag,
|
|
||||||
options: { enabled: canFollowTags }
|
|
||||||
})
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const mutation = useTagsMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
haptics('Success')
|
|
||||||
refetch()
|
|
||||||
const queryKeyFollowedTags: QueryKeyFollowedTags = ['FollowedTags']
|
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeyFollowedTags })
|
|
||||||
},
|
|
||||||
onError: (err: any, { to }) => {
|
|
||||||
displayMessage({
|
|
||||||
type: 'error',
|
|
||||||
message: t('common:message.error.message', {
|
|
||||||
function: to
|
|
||||||
? t('screenTabs:shared.hashtag.follow')
|
|
||||||
: t('screenTabs:shared.hashtag.unfollow')
|
|
||||||
}),
|
|
||||||
...(err.status &&
|
|
||||||
typeof err.status === 'number' &&
|
|
||||||
err.data &&
|
|
||||||
err.data.error &&
|
|
||||||
typeof err.data.error === 'string' && {
|
|
||||||
description: err.data.error
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canFollowTags) return
|
if (!canFollowTags && !canFilterTag) return
|
||||||
|
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderRight
|
<DropdownMenu.Root>
|
||||||
loading={isFetching || mutation.isLoading}
|
<DropdownMenu.Trigger>
|
||||||
type='text'
|
<HeaderRight content='more-horizontal' onPress={() => {}} />
|
||||||
content={
|
</DropdownMenu.Trigger>
|
||||||
data?.following
|
|
||||||
? t('screenTabs:shared.hashtag.unfollow')
|
<DropdownMenu.Content>
|
||||||
: t('screenTabs:shared.hashtag.follow')
|
{[mHashtag].map((menu, i) => (
|
||||||
}
|
<Fragment key={i}>
|
||||||
onPress={() =>
|
{menu.map((group, index) => (
|
||||||
typeof data?.following === 'boolean' &&
|
<DropdownMenu.Group key={index}>
|
||||||
mutation.mutate({ tag: hashtag, to: !data.following })
|
{group.map(item => {
|
||||||
}
|
switch (item.type) {
|
||||||
/>
|
case 'item':
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Item key={item.key} {...item.props}>
|
||||||
|
<DropdownMenu.ItemTitle children={item.title} />
|
||||||
|
{item.icon ? (
|
||||||
|
<DropdownMenu.ItemIcon ios={{ name: item.icon }} />
|
||||||
|
) : null}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</DropdownMenu.Group>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}, [canFollowTags, data?.following, isFetching])
|
}, [mHashtag])
|
||||||
|
|
||||||
return <Timeline queryKey={queryKey} />
|
return <Timeline queryKey={queryKey} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import TabSharedSearch from '@screens/Tabs/Shared/Search'
|
||||||
import TabSharedToot from '@screens/Tabs/Shared/Toot'
|
import TabSharedToot from '@screens/Tabs/Shared/Toot'
|
||||||
import TabSharedUsers from '@screens/Tabs/Shared/Users'
|
import TabSharedUsers from '@screens/Tabs/Shared/Users'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import TabSharedFilter from './Filter'
|
||||||
|
|
||||||
const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNavigator> }) => {
|
const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNavigator> }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -28,6 +29,12 @@ const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNaviga
|
||||||
name='Tab-Shared-Attachments'
|
name='Tab-Shared-Attachments'
|
||||||
component={TabSharedAttachments}
|
component={TabSharedAttachments}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
key='Tab-Shared-Filter'
|
||||||
|
name='Tab-Shared-Filter'
|
||||||
|
component={TabSharedFilter}
|
||||||
|
options={{ presentation: 'modal' }}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key='Tab-Shared-Hashtag'
|
key='Tab-Shared-Hashtag'
|
||||||
name='Tab-Shared-Hashtag'
|
name='Tab-Shared-Hashtag'
|
||||||
|
|
|
@ -99,7 +99,10 @@ export type TabSharedStackParamList = {
|
||||||
}
|
}
|
||||||
'Tab-Shared-Account-In-Lists': { account: Pick<Mastodon.Account, 'id' | 'username'> }
|
'Tab-Shared-Account-In-Lists': { account: Pick<Mastodon.Account, 'id' | 'username'> }
|
||||||
'Tab-Shared-Attachments': { account: Mastodon.Account; queryKey?: QueryKeyTimeline }
|
'Tab-Shared-Attachments': { account: Mastodon.Account; queryKey?: QueryKeyTimeline }
|
||||||
'Tab-Shared-Hashtag': { hashtag: Mastodon.Tag['name']; queryKey?: QueryKeyTimeline }
|
'Tab-Shared-Filter':
|
||||||
|
| { source: 'status'; status: Mastodon.Status }
|
||||||
|
| { source: 'hashtag'; tag_name: Mastodon.Tag['name'] }
|
||||||
|
'Tab-Shared-Hashtag': { tag_name: Mastodon.Tag['name']; queryKey?: QueryKeyTimeline }
|
||||||
'Tab-Shared-History': { status: Mastodon.Status; detectedLanguage: string }
|
'Tab-Shared-History': { status: Mastodon.Status; detectedLanguage: string }
|
||||||
'Tab-Shared-Report': {
|
'Tab-Shared-Report': {
|
||||||
account: Pick<Mastodon.Account, 'id' | 'acct' | 'username' | 'url'>
|
account: Pick<Mastodon.Account, 'id' | 'acct' | 'username' | 'url'>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
import haptics from '@components/haptics'
|
||||||
|
import { QueryFunctionContext, useMutation, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||||
import apiInstance from '@utils/api/instance'
|
import apiInstance from '@utils/api/instance'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
|
|
||||||
|
@ -28,6 +29,73 @@ const useFilterQuery = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----- */
|
||||||
|
|
||||||
|
type MutationVarsFilter = { filter: Mastodon.Filter<'v2'> } & (
|
||||||
|
| { source: 'status'; action: 'add'; status: Mastodon.Status }
|
||||||
|
| { source: 'status'; action: 'remove'; status: Mastodon.Status }
|
||||||
|
| { source: 'keyword'; action: 'add'; keyword: string }
|
||||||
|
| { source: 'keyword'; action: 'remove'; keyword: string }
|
||||||
|
)
|
||||||
|
|
||||||
|
const mutationFunction = async (params: MutationVarsFilter) => {
|
||||||
|
switch (params.source) {
|
||||||
|
case 'status':
|
||||||
|
switch (params.action) {
|
||||||
|
case 'add':
|
||||||
|
return apiInstance({
|
||||||
|
method: 'post',
|
||||||
|
version: 'v2',
|
||||||
|
url: `filters/${params.filter.id}/statuses`,
|
||||||
|
body: { status_id: params.status.id }
|
||||||
|
})
|
||||||
|
case 'remove':
|
||||||
|
for (const status of params.filter.statuses) {
|
||||||
|
if (status.status_id === params.status.id) {
|
||||||
|
await apiInstance({
|
||||||
|
method: 'delete',
|
||||||
|
version: 'v2',
|
||||||
|
url: `filters/statuses/${status.id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'keyword':
|
||||||
|
switch (params.action) {
|
||||||
|
case 'add':
|
||||||
|
return apiInstance({
|
||||||
|
method: 'post',
|
||||||
|
version: 'v2',
|
||||||
|
url: `filters/${params.filter.id}/keywords`,
|
||||||
|
body: { keyword: params.keyword, whole_word: true }
|
||||||
|
})
|
||||||
|
case 'remove':
|
||||||
|
for (const keyword of params.filter.keywords) {
|
||||||
|
if (keyword.keyword === params.keyword) {
|
||||||
|
await apiInstance({
|
||||||
|
method: 'delete',
|
||||||
|
version: 'v2',
|
||||||
|
url: `filters/keywords/${keyword.id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useFilterMutation = () => {
|
||||||
|
return useMutation<any, AxiosError, MutationVarsFilter>(mutationFunction, {
|
||||||
|
onSuccess: () => haptics('Light'),
|
||||||
|
onError: () => haptics('Error')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- */
|
||||||
|
|
||||||
export type QueryKeyFilters = ['Filters', { version: 'v1' | 'v2' }]
|
export type QueryKeyFilters = ['Filters', { version: 'v1' | 'v2' }]
|
||||||
|
|
||||||
const filtersQueryFunction = async <T extends 'v1' | 'v2' = 'v1'>({
|
const filtersQueryFunction = async <T extends 'v1' | 'v2' = 'v1'>({
|
||||||
|
@ -54,4 +122,4 @@ const useFiltersQuery = <T extends 'v1' | 'v2' = 'v1'>(params?: {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useFilterQuery, useFiltersQuery }
|
export { useFilterQuery, useFilterMutation, useFiltersQuery }
|
||||||
|
|
|
@ -60,32 +60,33 @@ const useFollowedTagsQuery = (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryKeyTags = ['Tags', { tag: string }]
|
export type QueryKeyTag = ['Tag', { tag_name: string }]
|
||||||
const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyTags>) => {
|
const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyTag>) => {
|
||||||
const { tag } = queryKey[1]
|
const { tag_name } = queryKey[1]
|
||||||
|
|
||||||
const res = await apiInstance<Mastodon.Tag>({ method: 'get', url: `tags/${tag}` })
|
const res = await apiInstance<Mastodon.Tag>({ method: 'get', url: `tags/${tag_name}` })
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
const useTagsQuery = ({
|
const useTagQuery = ({
|
||||||
options,
|
tag_name,
|
||||||
...queryKeyParams
|
options
|
||||||
}: QueryKeyTags[1] & {
|
}: {
|
||||||
|
tag_name: Mastodon.Tag['name']
|
||||||
options?: UseQueryOptions<Mastodon.Tag, AxiosError>
|
options?: UseQueryOptions<Mastodon.Tag, AxiosError>
|
||||||
}) => {
|
}) => {
|
||||||
const queryKey: QueryKeyTags = ['Tags', { ...queryKeyParams }]
|
const queryKey: QueryKeyTag = ['Tag', { tag_name }]
|
||||||
return useQuery(queryKey, queryFunction, options)
|
return useQuery(queryKey, queryFunction, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MutationVarsAnnouncement = { tag: string; to: boolean }
|
type MutationVarsTag = { tag_name: Mastodon.Tag['name']; to: boolean }
|
||||||
const mutationFunction = async ({ tag, to }: MutationVarsAnnouncement) => {
|
const mutationFunction = async ({ tag_name, to }: MutationVarsTag) => {
|
||||||
return apiInstance<{}>({
|
return apiInstance<{}>({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `tags/${tag}/${to ? 'follow' : 'unfollow'}`
|
url: `tags/${tag_name}/${to ? 'follow' : 'unfollow'}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const useTagsMutation = (options: UseMutationOptions<{}, AxiosError, MutationVarsAnnouncement>) => {
|
const useTagMutation = (options: UseMutationOptions<{}, AxiosError, MutationVarsTag>) => {
|
||||||
return useMutation(mutationFunction, options)
|
return useMutation(mutationFunction, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useFollowedTagsQuery, useTagsQuery, useTagsMutation }
|
export { useFollowedTagsQuery, useTagQuery, useTagMutation }
|
||||||
|
|
|
@ -33,7 +33,7 @@ export type QueryKeyTimeline = [
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
page: 'Hashtag'
|
page: 'Hashtag'
|
||||||
hashtag: Mastodon.Tag['name']
|
tag_name: Mastodon.Tag['name']
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
page: 'List'
|
page: 'List'
|
||||||
|
@ -219,7 +219,7 @@ export const queryFunctionTimeline = async ({
|
||||||
case 'Hashtag':
|
case 'Hashtag':
|
||||||
return apiInstance<Mastodon.Status[]>({
|
return apiInstance<Mastodon.Status[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `timelines/tag/${page.hashtag}`,
|
url: `timelines/tag/${page.tag_name}`,
|
||||||
params
|
params
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue