mirror of
https://github.com/tooot-app/app
synced 2025-04-15 02:42:04 +02:00
Fixed #476
This commit is contained in:
parent
6a9f951dba
commit
20a55efb9c
@ -168,6 +168,7 @@ export interface Props {
|
|||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
disableDetails?: boolean
|
disableDetails?: boolean
|
||||||
selectable?: boolean
|
selectable?: boolean
|
||||||
|
setSpoilerExpanded?: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ParseHTML = React.memo(
|
const ParseHTML = React.memo(
|
||||||
@ -183,7 +184,8 @@ const ParseHTML = React.memo(
|
|||||||
expandHint,
|
expandHint,
|
||||||
highlighted = false,
|
highlighted = false,
|
||||||
disableDetails = false,
|
disableDetails = false,
|
||||||
selectable = false
|
selectable = false,
|
||||||
|
setSpoilerExpanded
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||||
const adaptedFontsize = adaptiveScale(
|
const adaptedFontsize = adaptiveScale(
|
||||||
@ -253,6 +255,9 @@ const ParseHTML = React.memo(
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
setExpanded(!expanded)
|
setExpanded(!expanded)
|
||||||
|
if (setSpoilerExpanded) {
|
||||||
|
setSpoilerExpanded(!expanded)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -4,90 +4,52 @@ import { useNavigation } from '@react-navigation/native'
|
|||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
|
||||||
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 { isEqual } from 'lodash'
|
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { useMutation, useQueryClient } from 'react-query'
|
import { useMutation, useQueryClient } from 'react-query'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import TimelineActions from './Shared/Actions'
|
import TimelineActions from './Shared/Actions'
|
||||||
import TimelineContent from './Shared/Content'
|
import TimelineContent from './Shared/Content'
|
||||||
|
import StatusContext from './Shared/Context'
|
||||||
import TimelineHeaderConversation from './Shared/HeaderConversation'
|
import TimelineHeaderConversation from './Shared/HeaderConversation'
|
||||||
import TimelinePoll from './Shared/Poll'
|
import TimelinePoll from './Shared/Poll'
|
||||||
|
|
||||||
const Avatars: React.FC<{ accounts: Mastodon.Account[] }> = ({ accounts }) => {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
borderRadius: 4,
|
|
||||||
overflow: 'hidden',
|
|
||||||
marginRight: StyleConstants.Spacing.S,
|
|
||||||
width: StyleConstants.Avatar.M,
|
|
||||||
height: StyleConstants.Avatar.M,
|
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{accounts.slice(0, 4).map(account => (
|
|
||||||
<GracefullyImage
|
|
||||||
key={account.id}
|
|
||||||
uri={{ original: account.avatar, static: account.avatar_static }}
|
|
||||||
dimension={{
|
|
||||||
width: StyleConstants.Avatar.M,
|
|
||||||
height:
|
|
||||||
accounts.length > 2
|
|
||||||
? StyleConstants.Avatar.M / 2
|
|
||||||
: StyleConstants.Avatar.M
|
|
||||||
}}
|
|
||||||
style={{ flex: 1, flexBasis: '50%' }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
conversation: Mastodon.Conversation
|
conversation: Mastodon.Conversation
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineConversation = React.memo(
|
const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlighted = false }) => {
|
||||||
({ conversation, queryKey, highlighted = false }: Props) => {
|
const { colors } = useTheme()
|
||||||
const instanceAccount = useSelector(
|
|
||||||
getInstanceAccount,
|
|
||||||
(prev, next) => prev?.id === next?.id
|
|
||||||
)
|
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const fireMutation = useCallback(() => {
|
const fireMutation = useCallback(() => {
|
||||||
return apiInstance<Mastodon.Conversation>({
|
return apiInstance<Mastodon.Conversation>({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `conversations/${conversation.id}/read`
|
url: `conversations/${conversation.id}/read`
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
const { mutate } = useMutation(fireMutation, {
|
|
||||||
onSettled: () => {
|
|
||||||
queryClient.invalidateQueries(queryKey)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
}, [])
|
||||||
|
const { mutate } = useMutation(fireMutation, {
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries(queryKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const navigation =
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const onPress = useCallback(() => {
|
||||||
const onPress = useCallback(() => {
|
if (conversation.last_status) {
|
||||||
if (conversation.last_status) {
|
conversation.unread && mutate()
|
||||||
conversation.unread && mutate()
|
navigation.push('Tab-Shared-Toot', {
|
||||||
navigation.push('Tab-Shared-Toot', {
|
toot: conversation.last_status,
|
||||||
toot: conversation.last_status,
|
rootQueryKey: queryKey
|
||||||
rootQueryKey: queryKey
|
})
|
||||||
})
|
}
|
||||||
}
|
}, [])
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<StatusContext.Provider value={{ queryKey, status: conversation.last_status }}>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
@ -100,19 +62,39 @@ const TimelineConversation = React.memo(
|
|||||||
conversation.unread && {
|
conversation.unread && {
|
||||||
borderLeftWidth: StyleConstants.Spacing.XS,
|
borderLeftWidth: StyleConstants.Spacing.XS,
|
||||||
borderLeftColor: colors.blue,
|
borderLeftColor: colors.blue,
|
||||||
paddingLeft:
|
paddingLeft: StyleConstants.Spacing.Global.PagePadding - StyleConstants.Spacing.XS
|
||||||
StyleConstants.Spacing.Global.PagePadding -
|
|
||||||
StyleConstants.Spacing.XS
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
||||||
<Avatars accounts={conversation.accounts} />
|
<View
|
||||||
<TimelineHeaderConversation
|
style={{
|
||||||
queryKey={queryKey}
|
borderRadius: 4,
|
||||||
conversation={conversation}
|
overflow: 'hidden',
|
||||||
/>
|
marginRight: StyleConstants.Spacing.S,
|
||||||
|
width: StyleConstants.Avatar.M,
|
||||||
|
height: StyleConstants.Avatar.M,
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{conversation.accounts.slice(0, 4).map(account => (
|
||||||
|
<GracefullyImage
|
||||||
|
key={account.id}
|
||||||
|
uri={{ original: account.avatar, static: account.avatar_static }}
|
||||||
|
dimension={{
|
||||||
|
width: StyleConstants.Avatar.M,
|
||||||
|
height:
|
||||||
|
conversation.accounts.length > 2
|
||||||
|
? StyleConstants.Avatar.M / 2
|
||||||
|
: StyleConstants.Avatar.M
|
||||||
|
}}
|
||||||
|
style={{ flex: 1, flexBasis: '50%' }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<TimelineHeaderConversation conversation={conversation} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{conversation.last_status ? (
|
{conversation.last_status ? (
|
||||||
@ -120,40 +102,19 @@ const TimelineConversation = React.memo(
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
||||||
paddingLeft: highlighted
|
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
? 0
|
|
||||||
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TimelineContent
|
<TimelineContent />
|
||||||
status={conversation.last_status}
|
<TimelinePoll />
|
||||||
highlighted={highlighted}
|
|
||||||
/>
|
|
||||||
{conversation.last_status.poll ? (
|
|
||||||
<TimelinePoll
|
|
||||||
queryKey={queryKey}
|
|
||||||
statusId={conversation.last_status.id}
|
|
||||||
poll={conversation.last_status.poll}
|
|
||||||
reblog={false}
|
|
||||||
sameAccount={
|
|
||||||
conversation.last_status.id === instanceAccount?.id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
<TimelineActions
|
|
||||||
queryKey={queryKey}
|
<TimelineActions />
|
||||||
status={conversation.last_status}
|
|
||||||
highlighted={highlighted}
|
|
||||||
accts={conversation.accounts.map(account => account.acct)}
|
|
||||||
reblog={false}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
</StatusContext.Provider>
|
||||||
},
|
)
|
||||||
(prev, next) => isEqual(prev.conversation, next.conversation)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
export default TimelineConversation
|
export default TimelineConversation
|
||||||
|
@ -16,11 +16,11 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
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 { uniqBy } from 'lodash'
|
import React, { useRef, useState } from 'react'
|
||||||
import React, { useRef } from 'react'
|
|
||||||
import { Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
import { Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import * as ContextMenu from 'zeego/context-menu'
|
import * as ContextMenu from 'zeego/context-menu'
|
||||||
|
import StatusContext from './Shared/Context'
|
||||||
import TimelineFeedback from './Shared/Feedback'
|
import TimelineFeedback from './Shared/Feedback'
|
||||||
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
@ -46,31 +46,28 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
disableOnPress = false
|
disableOnPress = false
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
|
|
||||||
const actualStatus = item.reblog ? item.reblog : item
|
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||||
|
|
||||||
const ownAccount = actualStatus.account?.id === instanceAccount?.id
|
|
||||||
|
|
||||||
|
const status = item.reblog ? item.reblog : item
|
||||||
|
const ownAccount = status.account?.id === instanceAccount?.id
|
||||||
|
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||||
|
instanceAccount.preferences['reading:expand:spoilers'] || false
|
||||||
|
)
|
||||||
|
const spoilerHidden = status.spoiler_text?.length
|
||||||
|
? !instanceAccount.preferences['reading:expand:spoilers'] && !spoilerExpanded
|
||||||
|
: false
|
||||||
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
||||||
content: '',
|
content: '',
|
||||||
complete: false
|
complete: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const filtered = queryKey && shouldFilter({ copiableContent, status: actualStatus, queryKey })
|
const filtered = queryKey && shouldFilter({ copiableContent, status, queryKey })
|
||||||
if (queryKey && filtered && !highlighted) {
|
if (queryKey && filtered && !highlighted) {
|
||||||
return <TimelineFiltered phrase={filtered} />
|
return <TimelineFiltered phrase={filtered} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPress = () => {
|
|
||||||
if (highlighted) return
|
|
||||||
navigation.push('Tab-Shared-Toot', {
|
|
||||||
toot: actualStatus,
|
|
||||||
rootQueryKey: queryKey
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainStyle: StyleProp<ViewStyle> = {
|
const mainStyle: StyleProp<ViewStyle> = {
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||||
backgroundColor: colors.backgroundDefault,
|
backgroundColor: colors.backgroundDefault,
|
||||||
@ -79,24 +76,14 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
const main = () => (
|
const main = () => (
|
||||||
<>
|
<>
|
||||||
{item.reblog ? (
|
{item.reblog ? (
|
||||||
<TimelineActioned action='reblog' account={item.account} />
|
<TimelineActioned action='reblog' />
|
||||||
) : item._pinned ? (
|
) : item._pinned ? (
|
||||||
<TimelineActioned action='pinned' account={item.account} />
|
<TimelineActioned action='pinned' />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
||||||
<TimelineAvatar
|
<TimelineAvatar />
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
<TimelineHeaderDefault />
|
||||||
account={actualStatus.account}
|
|
||||||
highlighted={highlighted}
|
|
||||||
/>
|
|
||||||
<TimelineHeaderDefault
|
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
|
||||||
rootQueryKey={rootQueryKey}
|
|
||||||
status={actualStatus}
|
|
||||||
highlighted={highlighted}
|
|
||||||
copiableContent={copiableContent}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@ -105,120 +92,103 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{typeof actualStatus.content === 'string' && actualStatus.content.length > 0 ? (
|
<TimelineContent setSpoilerExpanded={setSpoilerExpanded} />
|
||||||
<TimelineContent
|
<TimelinePoll />
|
||||||
status={actualStatus}
|
<TimelineAttachment />
|
||||||
highlighted={highlighted}
|
<TimelineCard />
|
||||||
disableDetails={disableDetails}
|
<TimelineFullConversation />
|
||||||
/>
|
<TimelineTranslate />
|
||||||
) : null}
|
<TimelineFeedback />
|
||||||
{queryKey && actualStatus.poll ? (
|
|
||||||
<TimelinePoll
|
|
||||||
queryKey={queryKey}
|
|
||||||
rootQueryKey={rootQueryKey}
|
|
||||||
statusId={actualStatus.id}
|
|
||||||
poll={actualStatus.poll}
|
|
||||||
reblog={item.reblog ? true : false}
|
|
||||||
sameAccount={ownAccount}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{!disableDetails &&
|
|
||||||
Array.isArray(actualStatus.media_attachments) &&
|
|
||||||
actualStatus.media_attachments.length ? (
|
|
||||||
<TimelineAttachment status={actualStatus} />
|
|
||||||
) : null}
|
|
||||||
{!disableDetails && actualStatus.card ? <TimelineCard card={actualStatus.card} /> : null}
|
|
||||||
{!disableDetails ? (
|
|
||||||
<TimelineFullConversation queryKey={queryKey} status={actualStatus} />
|
|
||||||
) : null}
|
|
||||||
<TimelineTranslate status={actualStatus} highlighted={highlighted} />
|
|
||||||
<TimelineFeedback status={actualStatus} highlighted={highlighted} />
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{queryKey && !disableDetails ? (
|
<TimelineActions />
|
||||||
<TimelineActions
|
|
||||||
queryKey={queryKey}
|
|
||||||
rootQueryKey={rootQueryKey}
|
|
||||||
highlighted={highlighted}
|
|
||||||
status={actualStatus}
|
|
||||||
ownAccount={ownAccount}
|
|
||||||
accts={uniqBy(
|
|
||||||
([actualStatus.account] as Mastodon.Account[] & Mastodon.Mention[])
|
|
||||||
.concat(actualStatus.mentions)
|
|
||||||
.filter(d => d?.id !== instanceAccount?.id),
|
|
||||||
d => d?.id
|
|
||||||
).map(d => d?.acct)}
|
|
||||||
reblog={item.reblog ? true : false}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const mShare = menuShare({
|
const mShare = menuShare({
|
||||||
visibility: actualStatus.visibility,
|
visibility: status.visibility,
|
||||||
type: 'status',
|
type: 'status',
|
||||||
url: actualStatus.url || actualStatus.uri,
|
url: status.url || status.uri,
|
||||||
copiableContent
|
copiableContent
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status: actualStatus, queryKey, rootQueryKey })
|
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||||
const mInstance = menuInstance({ status: actualStatus, queryKey, rootQueryKey })
|
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
||||||
|
|
||||||
return disableOnPress ? (
|
return (
|
||||||
<View style={mainStyle}>{main()}</View>
|
<StatusContext.Provider
|
||||||
) : (
|
value={{
|
||||||
<>
|
queryKey,
|
||||||
<ContextMenu.Root>
|
rootQueryKey,
|
||||||
<ContextMenu.Trigger>
|
status,
|
||||||
<Pressable
|
isReblog: !!item.reblog,
|
||||||
accessible={highlighted ? false : true}
|
ownAccount,
|
||||||
style={mainStyle}
|
spoilerHidden,
|
||||||
onPress={onPress}
|
copiableContent,
|
||||||
onLongPress={() => {}}
|
highlighted,
|
||||||
children={main()}
|
disableDetails,
|
||||||
/>
|
disableOnPress
|
||||||
</ContextMenu.Trigger>
|
}}
|
||||||
|
>
|
||||||
|
{disableOnPress ? (
|
||||||
|
<View style={mainStyle}>{main()}</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ContextMenu.Root>
|
||||||
|
<ContextMenu.Trigger>
|
||||||
|
<Pressable
|
||||||
|
accessible={highlighted ? false : true}
|
||||||
|
style={mainStyle}
|
||||||
|
disabled={highlighted}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push('Tab-Shared-Toot', {
|
||||||
|
toot: status,
|
||||||
|
rootQueryKey: queryKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onLongPress={() => {}}
|
||||||
|
children={main()}
|
||||||
|
/>
|
||||||
|
</ContextMenu.Trigger>
|
||||||
|
|
||||||
<ContextMenu.Content>
|
<ContextMenu.Content>
|
||||||
{mShare.map((mGroup, index) => (
|
{mShare.map((mGroup, index) => (
|
||||||
<ContextMenu.Group key={index}>
|
<ContextMenu.Group key={index}>
|
||||||
{mGroup.map(menu => (
|
{mGroup.map(menu => (
|
||||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||||
<ContextMenu.ItemTitle children={menu.title} />
|
<ContextMenu.ItemTitle children={menu.title} />
|
||||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
|
))}
|
||||||
|
</ContextMenu.Group>
|
||||||
))}
|
))}
|
||||||
</ContextMenu.Group>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{mStatus.map((mGroup, index) => (
|
{mStatus.map((mGroup, index) => (
|
||||||
<ContextMenu.Group key={index}>
|
<ContextMenu.Group key={index}>
|
||||||
{mGroup.map(menu => (
|
{mGroup.map(menu => (
|
||||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||||
<ContextMenu.ItemTitle children={menu.title} />
|
<ContextMenu.ItemTitle children={menu.title} />
|
||||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
|
))}
|
||||||
|
</ContextMenu.Group>
|
||||||
))}
|
))}
|
||||||
</ContextMenu.Group>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{mInstance.map((mGroup, index) => (
|
{mInstance.map((mGroup, index) => (
|
||||||
<ContextMenu.Group key={index}>
|
<ContextMenu.Group key={index}>
|
||||||
{mGroup.map(menu => (
|
{mGroup.map(menu => (
|
||||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||||
<ContextMenu.ItemTitle children={menu.title} />
|
<ContextMenu.ItemTitle children={menu.title} />
|
||||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
|
))}
|
||||||
|
</ContextMenu.Group>
|
||||||
))}
|
))}
|
||||||
</ContextMenu.Group>
|
</ContextMenu.Content>
|
||||||
))}
|
</ContextMenu.Root>
|
||||||
</ContextMenu.Content>
|
<TimelineHeaderAndroid />
|
||||||
</ContextMenu.Root>
|
</>
|
||||||
<TimelineHeaderAndroid
|
)}
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
</StatusContext.Provider>
|
||||||
rootQueryKey={rootQueryKey}
|
|
||||||
status={actualStatus}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,11 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
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 { uniqBy } from 'lodash'
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
import React, { useCallback, useRef } from 'react'
|
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import * as ContextMenu from 'zeego/context-menu'
|
import * as ContextMenu from 'zeego/context-menu'
|
||||||
|
import StatusContext from './Shared/Context'
|
||||||
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
||||||
@ -36,6 +36,17 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
highlighted = false
|
highlighted = false
|
||||||
}) => {
|
}) => {
|
||||||
|
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||||
|
|
||||||
|
const status = notification.status
|
||||||
|
const account = notification.status ? notification.status.account : notification.account
|
||||||
|
const ownAccount = notification.account?.id === instanceAccount?.id
|
||||||
|
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||||
|
instanceAccount.preferences['reading:expand:spoilers'] || false
|
||||||
|
)
|
||||||
|
const spoilerHidden = notification.status?.spoiler_text?.length
|
||||||
|
? !instanceAccount.preferences['reading:expand:spoilers'] && !spoilerExpanded
|
||||||
|
: false
|
||||||
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
||||||
content: '',
|
content: '',
|
||||||
complete: false
|
complete: false
|
||||||
@ -53,11 +64,8 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev?.id === next?.id)
|
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
|
|
||||||
const actualAccount = notification.status ? notification.status.account : notification.account
|
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
notification.status &&
|
notification.status &&
|
||||||
navigation.push('Tab-Shared-Toot', {
|
navigation.push('Tab-Shared-Toot', {
|
||||||
@ -70,11 +78,7 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{notification.type !== 'mention' ? (
|
{notification.type !== 'mention' ? (
|
||||||
<TimelineActioned
|
<TimelineActioned action={notification.type} isNotification account={account} />
|
||||||
action={notification.type}
|
|
||||||
account={notification.account}
|
|
||||||
notification
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@ -89,8 +93,8 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
||||||
<TimelineAvatar queryKey={queryKey} account={actualAccount} highlighted={highlighted} />
|
<TimelineAvatar account={account} />
|
||||||
<TimelineHeaderNotification queryKey={queryKey} notification={notification} />
|
<TimelineHeaderNotification notification={notification} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notification.status ? (
|
{notification.status ? (
|
||||||
@ -100,41 +104,16 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{notification.status.content.length > 0 ? (
|
<TimelineContent setSpoilerExpanded={setSpoilerExpanded} />
|
||||||
<TimelineContent status={notification.status} highlighted={highlighted} />
|
<TimelinePoll />
|
||||||
) : null}
|
<TimelineAttachment />
|
||||||
{notification.status.poll ? (
|
<TimelineCard />
|
||||||
<TimelinePoll
|
<TimelineFullConversation />
|
||||||
queryKey={queryKey}
|
|
||||||
statusId={notification.status.id}
|
|
||||||
poll={notification.status.poll}
|
|
||||||
reblog={false}
|
|
||||||
sameAccount={notification.account.id === instanceAccount?.id}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{notification.status.media_attachments.length > 0 ? (
|
|
||||||
<TimelineAttachment status={notification.status} />
|
|
||||||
) : null}
|
|
||||||
{notification.status.card ? <TimelineCard card={notification.status.card} /> : null}
|
|
||||||
<TimelineFullConversation queryKey={queryKey} status={notification.status} />
|
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notification.status ? (
|
<TimelineActions />
|
||||||
<TimelineActions
|
|
||||||
queryKey={queryKey}
|
|
||||||
status={notification.status}
|
|
||||||
highlighted={highlighted}
|
|
||||||
accts={uniqBy(
|
|
||||||
([notification.status.account] as Mastodon.Account[] & Mastodon.Mention[])
|
|
||||||
.concat(notification.status.mentions)
|
|
||||||
.filter(d => d?.id !== instanceAccount?.id),
|
|
||||||
d => d?.id
|
|
||||||
).map(d => d?.acct)}
|
|
||||||
reblog={false}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -149,7 +128,17 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
const mInstance = menuInstance({ status: notification.status, queryKey })
|
const mInstance = menuInstance({ status: notification.status, queryKey })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StatusContext.Provider
|
||||||
|
value={{
|
||||||
|
queryKey,
|
||||||
|
status,
|
||||||
|
isReblog: !!status?.reblog,
|
||||||
|
ownAccount,
|
||||||
|
spoilerHidden,
|
||||||
|
copiableContent,
|
||||||
|
highlighted
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ContextMenu.Root>
|
<ContextMenu.Root>
|
||||||
<ContextMenu.Trigger>
|
<ContextMenu.Trigger>
|
||||||
<Pressable
|
<Pressable
|
||||||
@ -199,8 +188,8 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
))}
|
))}
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
</ContextMenu.Root>
|
</ContextMenu.Root>
|
||||||
<TimelineHeaderAndroid queryKey={queryKey} status={notification.status} />
|
<TimelineHeaderAndroid />
|
||||||
</>
|
</StatusContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,163 +5,164 @@ import { StackNavigationProp } from '@react-navigation/stack'
|
|||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
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 from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account
|
action: Mastodon.Notification['type'] | 'reblog' | 'pinned'
|
||||||
action: Mastodon.Notification['type'] | ('reblog' | 'pinned')
|
isNotification?: boolean
|
||||||
notification?: boolean
|
account?: Mastodon.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineActioned = React.memo(
|
const TimelineActioned: React.FC<Props> = ({ action, isNotification, ...rest }) => {
|
||||||
({ account, action, notification = false }: Props) => {
|
const { status } = useContext(StatusContext)
|
||||||
const { t } = useTranslation('componentTimeline')
|
const account = isNotification ? rest.account : status?.account
|
||||||
const { colors } = useTheme()
|
if (!status || !account) return null
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
|
||||||
const name = account?.display_name || account?.username
|
|
||||||
const iconColor = colors.primaryDefault
|
|
||||||
|
|
||||||
const content = (content: string) => (
|
const { t } = useTranslation('componentTimeline')
|
||||||
<ParseEmojis content={content} emojis={account.emojis} size='S' />
|
const { colors } = useTheme()
|
||||||
)
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
|
const name = account?.display_name || account?.username
|
||||||
|
const iconColor = colors.primaryDefault
|
||||||
|
|
||||||
const onPress = () => navigation.push('Tab-Shared-Account', { account })
|
const content = (content: string) => (
|
||||||
|
<ParseEmojis content={content} emojis={account.emojis} size='S' />
|
||||||
|
)
|
||||||
|
|
||||||
const children = () => {
|
const onPress = () => navigation.push('Tab-Shared-Account', { account })
|
||||||
switch (action) {
|
|
||||||
case 'pinned':
|
const children = () => {
|
||||||
return (
|
switch (action) {
|
||||||
<>
|
case 'pinned':
|
||||||
<Icon
|
return (
|
||||||
name='Anchor'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='Anchor'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
{content(t('shared.actioned.pinned'))}
|
style={styles.icon}
|
||||||
</>
|
/>
|
||||||
)
|
{content(t('shared.actioned.pinned'))}
|
||||||
case 'favourite':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'favourite':
|
||||||
<Icon
|
return (
|
||||||
name='Heart'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='Heart'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
<Pressable onPress={onPress}>
|
style={styles.icon}
|
||||||
{content(t('shared.actioned.favourite', { name }))}
|
/>
|
||||||
</Pressable>
|
<Pressable onPress={onPress}>
|
||||||
</>
|
{content(t('shared.actioned.favourite', { name }))}
|
||||||
)
|
</Pressable>
|
||||||
case 'follow':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'follow':
|
||||||
<Icon
|
return (
|
||||||
name='UserPlus'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='UserPlus'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
<Pressable onPress={onPress}>
|
style={styles.icon}
|
||||||
{content(t('shared.actioned.follow', { name }))}
|
/>
|
||||||
</Pressable>
|
<Pressable onPress={onPress}>
|
||||||
</>
|
{content(t('shared.actioned.follow', { name }))}
|
||||||
)
|
</Pressable>
|
||||||
case 'follow_request':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'follow_request':
|
||||||
<Icon
|
return (
|
||||||
name='UserPlus'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='UserPlus'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
<Pressable onPress={onPress}>
|
style={styles.icon}
|
||||||
{content(t('shared.actioned.follow_request', { name }))}
|
/>
|
||||||
</Pressable>
|
<Pressable onPress={onPress}>
|
||||||
</>
|
{content(t('shared.actioned.follow_request', { name }))}
|
||||||
)
|
</Pressable>
|
||||||
case 'poll':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'poll':
|
||||||
<Icon
|
return (
|
||||||
name='BarChart2'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='BarChart2'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
{content(t('shared.actioned.poll'))}
|
style={styles.icon}
|
||||||
</>
|
/>
|
||||||
)
|
{content(t('shared.actioned.poll'))}
|
||||||
case 'reblog':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'reblog':
|
||||||
<Icon
|
return (
|
||||||
name='Repeat'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='Repeat'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
<Pressable onPress={onPress}>
|
style={styles.icon}
|
||||||
{content(
|
/>
|
||||||
notification
|
<Pressable onPress={onPress}>
|
||||||
? t('shared.actioned.reblog.notification', { name })
|
{content(
|
||||||
: t('shared.actioned.reblog.default', { name })
|
isNotification
|
||||||
)}
|
? t('shared.actioned.reblog.notification', { name })
|
||||||
</Pressable>
|
: t('shared.actioned.reblog.default', { name })
|
||||||
</>
|
)}
|
||||||
)
|
</Pressable>
|
||||||
case 'status':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'status':
|
||||||
<Icon
|
return (
|
||||||
name='Activity'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='Activity'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
<Pressable onPress={onPress}>
|
style={styles.icon}
|
||||||
{content(t('shared.actioned.status', { name }))}
|
/>
|
||||||
</Pressable>
|
<Pressable onPress={onPress}>
|
||||||
</>
|
{content(t('shared.actioned.status', { name }))}
|
||||||
)
|
</Pressable>
|
||||||
case 'update':
|
</>
|
||||||
return (
|
)
|
||||||
<>
|
case 'update':
|
||||||
<Icon
|
return (
|
||||||
name='BarChart2'
|
<>
|
||||||
size={StyleConstants.Font.Size.S}
|
<Icon
|
||||||
color={iconColor}
|
name='BarChart2'
|
||||||
style={styles.icon}
|
size={StyleConstants.Font.Size.S}
|
||||||
/>
|
color={iconColor}
|
||||||
{content(t('shared.actioned.update'))}
|
style={styles.icon}
|
||||||
</>
|
/>
|
||||||
)
|
{content(t('shared.actioned.update'))}
|
||||||
default:
|
</>
|
||||||
return <></>
|
)
|
||||||
}
|
default:
|
||||||
|
return <></>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: StyleConstants.Spacing.S,
|
marginBottom: StyleConstants.Spacing.S,
|
||||||
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
|
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
|
||||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||||
}}
|
}}
|
||||||
>
|
children={children()}
|
||||||
{children()}
|
/>
|
||||||
</View>
|
)
|
||||||
)
|
}
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
icon: {
|
icon: {
|
||||||
|
@ -10,32 +10,22 @@ import {
|
|||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
useTimelineMutation
|
useTimelineMutation
|
||||||
} from '@utils/queryHooks/timeline'
|
} from '@utils/queryHooks/timeline'
|
||||||
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
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, { useCallback, useMemo } from 'react'
|
import { uniqBy } from 'lodash'
|
||||||
|
import React, { useCallback, useContext, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineActions: React.FC = () => {
|
||||||
queryKey: QueryKeyTimeline
|
const { queryKey, rootQueryKey, status, isReblog, ownAccount, highlighted, disableDetails } =
|
||||||
rootQueryKey?: QueryKeyTimeline
|
useContext(StatusContext)
|
||||||
highlighted: boolean
|
if (!queryKey || !status || disableDetails) return null
|
||||||
status: Mastodon.Status
|
|
||||||
ownAccount?: boolean
|
|
||||||
accts: Mastodon.Account['acct'][] // When replying to conversations
|
|
||||||
reblog: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimelineActions: React.FC<Props> = ({
|
|
||||||
queryKey,
|
|
||||||
rootQueryKey,
|
|
||||||
highlighted,
|
|
||||||
status,
|
|
||||||
ownAccount = false,
|
|
||||||
accts,
|
|
||||||
reblog
|
|
||||||
}) => {
|
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { colors, theme } = useTheme()
|
const { colors, theme } = useTheme()
|
||||||
@ -83,16 +73,21 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onPressReply = useCallback(
|
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||||
() =>
|
const onPressReply = useCallback(() => {
|
||||||
navigation.navigate('Screen-Compose', {
|
const accts = uniqBy(
|
||||||
type: 'reply',
|
([status.account] as Mastodon.Account[] & Mastodon.Mention[])
|
||||||
incomingStatus: status,
|
.concat(status.mentions)
|
||||||
accts,
|
.filter(d => d?.id !== instanceAccount?.id),
|
||||||
queryKey
|
d => d?.id
|
||||||
}),
|
).map(d => d?.acct)
|
||||||
[status.replies_count]
|
navigation.navigate('Screen-Compose', {
|
||||||
)
|
type: 'reply',
|
||||||
|
incomingStatus: status,
|
||||||
|
accts,
|
||||||
|
queryKey
|
||||||
|
})
|
||||||
|
}, [status.replies_count])
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
const onPressReblog = useCallback(() => {
|
const onPressReblog = useCallback(() => {
|
||||||
if (!status.reblogged) {
|
if (!status.reblogged) {
|
||||||
@ -114,7 +109,7 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'reblogged',
|
property: 'reblogged',
|
||||||
currentValue: status.reblogged,
|
currentValue: status.reblogged,
|
||||||
@ -130,7 +125,7 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'reblogged',
|
property: 'reblogged',
|
||||||
currentValue: status.reblogged,
|
currentValue: status.reblogged,
|
||||||
@ -149,7 +144,7 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'reblogged',
|
property: 'reblogged',
|
||||||
currentValue: status.reblogged,
|
currentValue: status.reblogged,
|
||||||
@ -166,7 +161,7 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'favourited',
|
property: 'favourited',
|
||||||
currentValue: status.favourited,
|
currentValue: status.favourited,
|
||||||
@ -181,7 +176,7 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'bookmarked',
|
property: 'bookmarked',
|
||||||
currentValue: status.bookmarked,
|
currentValue: status.bookmarked,
|
||||||
|
@ -10,51 +10,140 @@ import { RootStackParamList } from '@utils/navigation/navigators'
|
|||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import React, { useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineAttachment = () => {
|
||||||
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
const { status, disableDetails } = useContext(StatusContext)
|
||||||
}
|
if (
|
||||||
|
!status ||
|
||||||
|
disableDetails ||
|
||||||
|
!Array.isArray(status.media_attachments) ||
|
||||||
|
!status.media_attachments.length
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
|
||||||
const TimelineAttachment = React.memo(
|
const { t } = useTranslation('componentTimeline')
|
||||||
({ status }: Props) => {
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
|
||||||
|
|
||||||
const account = useSelector(
|
const account = useSelector(
|
||||||
getInstanceAccount,
|
getInstanceAccount,
|
||||||
(prev, next) =>
|
(prev, next) =>
|
||||||
prev.preferences['reading:expand:media'] === next.preferences['reading:expand:media']
|
prev.preferences['reading:expand:media'] === next.preferences['reading:expand:media']
|
||||||
)
|
)
|
||||||
const defaultSensitive = () => {
|
const defaultSensitive = () => {
|
||||||
switch (account.preferences['reading:expand:media']) {
|
switch (account.preferences['reading:expand:media']) {
|
||||||
case 'show_all':
|
case 'show_all':
|
||||||
return false
|
return false
|
||||||
case 'hide_all':
|
case 'hide_all':
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return status.sensitive
|
return status.sensitive
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const [sensitiveShown, setSensitiveShown] = useState(defaultSensitive())
|
}
|
||||||
|
const [sensitiveShown, setSensitiveShown] = useState(defaultSensitive())
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls'] =
|
const imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls'] = status.media_attachments
|
||||||
status.media_attachments
|
.map(attachment => {
|
||||||
.map(attachment => {
|
switch (attachment.type) {
|
||||||
|
case 'image':
|
||||||
|
return {
|
||||||
|
id: attachment.id,
|
||||||
|
preview_url: attachment.preview_url,
|
||||||
|
url: attachment.url,
|
||||||
|
remote_url: attachment.remote_url,
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
width: attachment.meta?.original?.width,
|
||||||
|
height: attachment.meta?.original?.height
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (
|
||||||
|
attachment.preview_url?.endsWith('.jpg') ||
|
||||||
|
attachment.preview_url?.endsWith('.jpeg') ||
|
||||||
|
attachment.preview_url?.endsWith('.png') ||
|
||||||
|
attachment.preview_url?.endsWith('.gif') ||
|
||||||
|
attachment.remote_url?.endsWith('.jpg') ||
|
||||||
|
attachment.remote_url?.endsWith('.jpeg') ||
|
||||||
|
attachment.remote_url?.endsWith('.png') ||
|
||||||
|
attachment.remote_url?.endsWith('.gif')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
id: attachment.id,
|
||||||
|
preview_url: attachment.preview_url,
|
||||||
|
url: attachment.url,
|
||||||
|
remote_url: attachment.remote_url,
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
width: attachment.meta?.original?.width,
|
||||||
|
height: attachment.meta?.original?.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(i => i)
|
||||||
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||||
|
const navigateToImagesViewer = (id: string) => {
|
||||||
|
navigation.navigate('Screen-ImagesViewer', { imageUrls, id })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginTop: StyleConstants.Spacing.S,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignContent: 'stretch'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status.media_attachments.map((attachment, index) => {
|
||||||
switch (attachment.type) {
|
switch (attachment.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return {
|
return (
|
||||||
id: attachment.id,
|
<AttachmentImage
|
||||||
preview_url: attachment.preview_url,
|
key={index}
|
||||||
url: attachment.url,
|
total={status.media_attachments.length}
|
||||||
remote_url: attachment.remote_url,
|
index={index}
|
||||||
blurhash: attachment.blurhash,
|
sensitiveShown={sensitiveShown}
|
||||||
width: attachment.meta?.original?.width,
|
image={attachment}
|
||||||
height: attachment.meta?.original?.height
|
navigateToImagesViewer={navigateToImagesViewer}
|
||||||
}
|
/>
|
||||||
|
)
|
||||||
|
case 'video':
|
||||||
|
return (
|
||||||
|
<AttachmentVideo
|
||||||
|
key={index}
|
||||||
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
video={attachment}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'gifv':
|
||||||
|
return (
|
||||||
|
<AttachmentVideo
|
||||||
|
key={index}
|
||||||
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
video={attachment}
|
||||||
|
gifv
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'audio':
|
||||||
|
return (
|
||||||
|
<AttachmentAudio
|
||||||
|
key={index}
|
||||||
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
audio={attachment}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
if (
|
if (
|
||||||
attachment.preview_url?.endsWith('.jpg') ||
|
attachment.preview_url?.endsWith('.jpg') ||
|
||||||
@ -66,176 +155,74 @@ const TimelineAttachment = React.memo(
|
|||||||
attachment.remote_url?.endsWith('.png') ||
|
attachment.remote_url?.endsWith('.png') ||
|
||||||
attachment.remote_url?.endsWith('.gif')
|
attachment.remote_url?.endsWith('.gif')
|
||||||
) {
|
) {
|
||||||
return {
|
|
||||||
id: attachment.id,
|
|
||||||
preview_url: attachment.preview_url,
|
|
||||||
url: attachment.url,
|
|
||||||
remote_url: attachment.remote_url,
|
|
||||||
blurhash: attachment.blurhash,
|
|
||||||
width: attachment.meta?.original?.width,
|
|
||||||
height: attachment.meta?.original?.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(i => i)
|
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
|
||||||
const navigateToImagesViewer = (id: string) => {
|
|
||||||
navigation.navigate('Screen-ImagesViewer', { imageUrls, id })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
marginTop: StyleConstants.Spacing.S,
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignContent: 'stretch'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{status.media_attachments.map((attachment, index) => {
|
|
||||||
switch (attachment.type) {
|
|
||||||
case 'image':
|
|
||||||
return (
|
return (
|
||||||
<AttachmentImage
|
<AttachmentImage
|
||||||
key={index}
|
key={index}
|
||||||
total={status.media_attachments.length}
|
total={status.media_attachments.length}
|
||||||
index={index}
|
index={index}
|
||||||
sensitiveShown={sensitiveShown}
|
sensitiveShown={sensitiveShown}
|
||||||
|
// @ts-ignore
|
||||||
image={attachment}
|
image={attachment}
|
||||||
navigateToImagesViewer={navigateToImagesViewer}
|
navigateToImagesViewer={navigateToImagesViewer}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'video':
|
} else {
|
||||||
return (
|
return (
|
||||||
<AttachmentVideo
|
<AttachmentUnsupported
|
||||||
key={index}
|
key={index}
|
||||||
total={status.media_attachments.length}
|
total={status.media_attachments.length}
|
||||||
index={index}
|
index={index}
|
||||||
sensitiveShown={sensitiveShown}
|
sensitiveShown={sensitiveShown}
|
||||||
video={attachment}
|
attachment={attachment}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'gifv':
|
}
|
||||||
return (
|
}
|
||||||
<AttachmentVideo
|
})}
|
||||||
key={index}
|
</View>
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
video={attachment}
|
|
||||||
gifv
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'audio':
|
|
||||||
return (
|
|
||||||
<AttachmentAudio
|
|
||||||
key={index}
|
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
audio={attachment}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
if (
|
|
||||||
attachment.preview_url?.endsWith('.jpg') ||
|
|
||||||
attachment.preview_url?.endsWith('.jpeg') ||
|
|
||||||
attachment.preview_url?.endsWith('.png') ||
|
|
||||||
attachment.preview_url?.endsWith('.gif') ||
|
|
||||||
attachment.remote_url?.endsWith('.jpg') ||
|
|
||||||
attachment.remote_url?.endsWith('.jpeg') ||
|
|
||||||
attachment.remote_url?.endsWith('.png') ||
|
|
||||||
attachment.remote_url?.endsWith('.gif')
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<AttachmentImage
|
|
||||||
key={index}
|
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
// @ts-ignore
|
|
||||||
image={attachment}
|
|
||||||
navigateToImagesViewer={navigateToImagesViewer}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<AttachmentUnsupported
|
|
||||||
key={index}
|
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
attachment={attachment}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{defaultSensitive() &&
|
{defaultSensitive() &&
|
||||||
(sensitiveShown ? (
|
(sensitiveShown ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
|
||||||
type='text'
|
|
||||||
content={t('shared.attachment.sensitive.button')}
|
|
||||||
overlay
|
|
||||||
onPress={() => {
|
|
||||||
layoutAnimation()
|
|
||||||
setSensitiveShown(false)
|
|
||||||
haptics('Light')
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
) : (
|
|
||||||
<Button
|
<Button
|
||||||
type='icon'
|
type='text'
|
||||||
content='EyeOff'
|
content={t('shared.attachment.sensitive.button')}
|
||||||
round
|
|
||||||
overlay
|
overlay
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setSensitiveShown(true)
|
layoutAnimation()
|
||||||
|
setSensitiveShown(false)
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
}}
|
}}
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: StyleConstants.Spacing.S * 2,
|
|
||||||
left: StyleConstants.Spacing.S
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
))}
|
</Pressable>
|
||||||
</View>
|
) : (
|
||||||
)
|
<Button
|
||||||
},
|
type='icon'
|
||||||
(prev, next) => {
|
content='EyeOff'
|
||||||
let isEqual = true
|
round
|
||||||
|
overlay
|
||||||
if (prev.status.media_attachments.length !== next.status.media_attachments.length) {
|
onPress={() => {
|
||||||
isEqual = false
|
setSensitiveShown(true)
|
||||||
return isEqual
|
haptics('Light')
|
||||||
}
|
}}
|
||||||
|
style={{
|
||||||
prev.status.media_attachments.forEach((attachment, index) => {
|
position: 'absolute',
|
||||||
if (attachment.preview_url !== next.status.media_attachments[index].preview_url) {
|
top: StyleConstants.Spacing.S * 2,
|
||||||
isEqual = false
|
left: StyleConstants.Spacing.S
|
||||||
}
|
}}
|
||||||
})
|
/>
|
||||||
|
))}
|
||||||
return isEqual
|
</View>
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
export default TimelineAttachment
|
export default TimelineAttachment
|
||||||
|
@ -2,37 +2,37 @@ import GracefullyImage from '@components/GracefullyImage'
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey?: QueryKeyTimeline
|
account?: Mastodon.Account
|
||||||
account: Mastodon.Account
|
|
||||||
highlighted: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineAvatar = React.memo(({ queryKey, account, highlighted }: Props) => {
|
const TimelineAvatar: React.FC<Props> = ({ account }) => {
|
||||||
|
const { status, highlighted, disableOnPress } = useContext(StatusContext)
|
||||||
|
const actualAccount = account || status?.account
|
||||||
|
if (!actualAccount) return null
|
||||||
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
// Need to fix go back root
|
|
||||||
const onPress = useCallback(() => {
|
|
||||||
queryKey && navigation.push('Tab-Shared-Account', { account })
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
{...(highlighted && {
|
{...(highlighted && {
|
||||||
accessibilityLabel: t('shared.avatar.accessibilityLabel', {
|
accessibilityLabel: t('shared.avatar.accessibilityLabel', {
|
||||||
name: account.display_name
|
name: actualAccount.display_name
|
||||||
}),
|
}),
|
||||||
accessibilityHint: t('shared.avatar.accessibilityHint', {
|
accessibilityHint: t('shared.avatar.accessibilityHint', {
|
||||||
name: account.display_name
|
name: actualAccount.display_name
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
onPress={onPress}
|
onPress={() =>
|
||||||
uri={{ original: account?.avatar, static: account?.avatar_static }}
|
!disableOnPress && navigation.push('Tab-Shared-Account', { account: actualAccount })
|
||||||
|
}
|
||||||
|
uri={{ original: actualAccount.avatar, static: actualAccount.avatar_static }}
|
||||||
dimension={{
|
dimension={{
|
||||||
width: StyleConstants.Avatar.M,
|
width: StyleConstants.Avatar.M,
|
||||||
height: StyleConstants.Avatar.M
|
height: StyleConstants.Avatar.M
|
||||||
@ -44,6 +44,6 @@ const TimelineAvatar = React.memo(({ queryKey, account, highlighted }: Props) =>
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
export default TimelineAvatar
|
export default TimelineAvatar
|
||||||
|
@ -9,23 +9,23 @@ import { useSearchQuery } from '@utils/queryHooks/search'
|
|||||||
import { useStatusQuery } from '@utils/queryHooks/status'
|
import { useStatusQuery } from '@utils/queryHooks/status'
|
||||||
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, { useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
import TimelineDefault from '../Default'
|
import TimelineDefault from '../Default'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineCard: React.FC = () => {
|
||||||
card: Pick<Mastodon.Card, 'url' | 'image' | 'blurhash' | 'title' | 'description'>
|
const { status, spoilerHidden, disableDetails } = useContext(StatusContext)
|
||||||
}
|
if (!status || !status.card) return null
|
||||||
|
|
||||||
const TimelineCard = React.memo(({ card }: Props) => {
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const isStatus = matchStatus(card.url)
|
const isStatus = matchStatus(status.card.url)
|
||||||
const [foundStatus, setFoundStatus] = useState<Mastodon.Status>()
|
const [foundStatus, setFoundStatus] = useState<Mastodon.Status>()
|
||||||
const isAccount = matchAccount(card.url)
|
const isAccount = matchAccount(status.card.url)
|
||||||
const [foundAccount, setFoundAccount] = useState<Mastodon.Account>()
|
const [foundAccount, setFoundAccount] = useState<Mastodon.Account>()
|
||||||
|
|
||||||
const searchQuery = useSearchQuery({
|
const searchQuery = useSearchQuery({
|
||||||
@ -38,7 +38,7 @@ const TimelineCard = React.memo(({ card }: Props) => {
|
|||||||
if (isStatus.sameInstance) {
|
if (isStatus.sameInstance) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
return card.url
|
return status.card.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAccount) {
|
if (isAccount) {
|
||||||
@ -49,7 +49,7 @@ const TimelineCard = React.memo(({ card }: Props) => {
|
|||||||
return isAccount.username
|
return isAccount.username
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return card.url
|
return status.card.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
@ -136,10 +136,10 @@ const TimelineCard = React.memo(({ card }: Props) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{card.image ? (
|
{status.card?.image ? (
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
uri={{ original: card.image }}
|
uri={{ original: status.card.image }}
|
||||||
blurhash={card.blurhash}
|
blurhash={status.card.blurhash}
|
||||||
style={{ flexBasis: StyleConstants.Font.LineHeight.M * 5 }}
|
style={{ flexBasis: StyleConstants.Font.LineHeight.M * 5 }}
|
||||||
imageStyle={{ borderTopLeftRadius: 6, borderBottomLeftRadius: 6 }}
|
imageStyle={{ borderTopLeftRadius: 6, borderBottomLeftRadius: 6 }}
|
||||||
/>
|
/>
|
||||||
@ -155,9 +155,9 @@ const TimelineCard = React.memo(({ card }: Props) => {
|
|||||||
fontWeight='Bold'
|
fontWeight='Bold'
|
||||||
testID='title'
|
testID='title'
|
||||||
>
|
>
|
||||||
{card.title}
|
{status.card?.title}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
{card.description ? (
|
{status.card?.description ? (
|
||||||
<CustomText
|
<CustomText
|
||||||
fontStyle='S'
|
fontStyle='S'
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
@ -167,17 +167,19 @@ const TimelineCard = React.memo(({ card }: Props) => {
|
|||||||
}}
|
}}
|
||||||
testID='description'
|
testID='description'
|
||||||
>
|
>
|
||||||
{card.description}
|
{status.card.description}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
) : null}
|
) : null}
|
||||||
<CustomText fontStyle='S' numberOfLines={1} style={{ color: colors.secondary }}>
|
<CustomText fontStyle='S' numberOfLines={1} style={{ color: colors.secondary }}>
|
||||||
{card.url}
|
{status.card?.url}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spoilerHidden || disableDetails) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
accessible
|
accessible
|
||||||
@ -192,10 +194,10 @@ const TimelineCard = React.memo(({ card }: Props) => {
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
borderColor: colors.border
|
borderColor: colors.border
|
||||||
}}
|
}}
|
||||||
onPress={async () => await openLink(card.url, navigation)}
|
onPress={async () => status.card && (await openLink(status.card.url, navigation))}
|
||||||
children={cardContent}
|
children={cardContent()}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
export default TimelineCard
|
export default TimelineCard
|
||||||
|
@ -1,52 +1,36 @@
|
|||||||
import { ParseHTML } from '@components/Parse'
|
import { ParseHTML } from '@components/Parse'
|
||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
status: Pick<Mastodon.Status, 'content' | 'spoiler_text' | 'emojis'> & {
|
setSpoilerExpanded?: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
mentions?: Mastodon.Status['mentions']
|
|
||||||
tags?: Mastodon.Status['tags']
|
|
||||||
}
|
|
||||||
highlighted?: boolean
|
|
||||||
disableDetails?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineContent = React.memo(
|
const TimelineContent: React.FC<Props> = ({ setSpoilerExpanded }) => {
|
||||||
({ status, highlighted = false, disableDetails = false }: Props) => {
|
const { status, highlighted, disableDetails } = useContext(StatusContext)
|
||||||
const { t } = useTranslation('componentTimeline')
|
if (!status || typeof status.content !== 'string' || !status.content.length) return null
|
||||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
|
||||||
|
|
||||||
return (
|
const { t } = useTranslation('componentTimeline')
|
||||||
<>
|
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||||
{status.spoiler_text ? (
|
|
||||||
<>
|
return (
|
||||||
<ParseHTML
|
<>
|
||||||
content={status.spoiler_text}
|
{status.spoiler_text?.length ? (
|
||||||
size={highlighted ? 'L' : 'M'}
|
<>
|
||||||
adaptiveSize
|
<ParseHTML
|
||||||
emojis={status.emojis}
|
content={status.spoiler_text}
|
||||||
mentions={status.mentions}
|
size={highlighted ? 'L' : 'M'}
|
||||||
tags={status.tags}
|
adaptiveSize
|
||||||
numberOfLines={999}
|
emojis={status.emojis}
|
||||||
highlighted={highlighted}
|
mentions={status.mentions}
|
||||||
disableDetails={disableDetails}
|
tags={status.tags}
|
||||||
/>
|
numberOfLines={999}
|
||||||
<ParseHTML
|
highlighted={highlighted}
|
||||||
content={status.content}
|
disableDetails={disableDetails}
|
||||||
size={highlighted ? 'L' : 'M'}
|
/>
|
||||||
adaptiveSize
|
|
||||||
emojis={status.emojis}
|
|
||||||
mentions={status.mentions}
|
|
||||||
tags={status.tags}
|
|
||||||
numberOfLines={instanceAccount.preferences['reading:expand:spoilers'] ? 999 : 1}
|
|
||||||
expandHint={t('shared.content.expandHint')}
|
|
||||||
highlighted={highlighted}
|
|
||||||
disableDetails={disableDetails}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<ParseHTML
|
<ParseHTML
|
||||||
content={status.content}
|
content={status.content}
|
||||||
size={highlighted ? 'L' : 'M'}
|
size={highlighted ? 'L' : 'M'}
|
||||||
@ -54,16 +38,27 @@ const TimelineContent = React.memo(
|
|||||||
emojis={status.emojis}
|
emojis={status.emojis}
|
||||||
mentions={status.mentions}
|
mentions={status.mentions}
|
||||||
tags={status.tags}
|
tags={status.tags}
|
||||||
numberOfLines={highlighted ? 999 : undefined}
|
numberOfLines={instanceAccount.preferences['reading:expand:spoilers'] ? 999 : 1}
|
||||||
|
expandHint={t('shared.content.expandHint')}
|
||||||
|
setSpoilerExpanded={setSpoilerExpanded}
|
||||||
|
highlighted={highlighted}
|
||||||
disableDetails={disableDetails}
|
disableDetails={disableDetails}
|
||||||
/>
|
/>
|
||||||
)}
|
</>
|
||||||
</>
|
) : (
|
||||||
)
|
<ParseHTML
|
||||||
},
|
content={status.content}
|
||||||
(prev, next) =>
|
size={highlighted ? 'L' : 'M'}
|
||||||
prev.status.content === next.status.content &&
|
adaptiveSize
|
||||||
prev.status.spoiler_text === next.status.spoiler_text
|
emojis={status.emojis}
|
||||||
)
|
mentions={status.mentions}
|
||||||
|
tags={status.tags}
|
||||||
|
numberOfLines={highlighted ? 999 : undefined}
|
||||||
|
disableDetails={disableDetails}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default TimelineContent
|
export default TimelineContent
|
||||||
|
24
src/components/Timeline/Shared/Context.tsx
Normal file
24
src/components/Timeline/Shared/Context.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import { createContext } from 'react'
|
||||||
|
|
||||||
|
type ContextType = {
|
||||||
|
queryKey?: QueryKeyTimeline
|
||||||
|
rootQueryKey?: QueryKeyTimeline
|
||||||
|
|
||||||
|
status?: Mastodon.Status
|
||||||
|
|
||||||
|
isReblog?: boolean
|
||||||
|
ownAccount?: boolean
|
||||||
|
spoilerHidden?: boolean
|
||||||
|
copiableContent?: React.MutableRefObject<{
|
||||||
|
content: string
|
||||||
|
complete: boolean
|
||||||
|
}>
|
||||||
|
|
||||||
|
highlighted?: boolean
|
||||||
|
disableDetails?: boolean
|
||||||
|
disableOnPress?: boolean
|
||||||
|
}
|
||||||
|
const StatusContext = createContext<ContextType>({} as ContextType)
|
||||||
|
|
||||||
|
export default StatusContext
|
@ -5,103 +5,92 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
|||||||
import { useStatusHistory } from '@utils/queryHooks/statusesHistory'
|
import { useStatusHistory } from '@utils/queryHooks/statusesHistory'
|
||||||
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 from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineFeedback = () => {
|
||||||
status: Pick<Mastodon.Status, 'id' | 'edited_at' | 'reblogs_count' | 'favourites_count'>
|
const { status, highlighted } = useContext(StatusContext)
|
||||||
highlighted: boolean
|
if (!status || !highlighted) return null
|
||||||
}
|
|
||||||
|
|
||||||
const TimelineFeedback = React.memo(
|
const { t } = useTranslation('componentTimeline')
|
||||||
({ status, highlighted }: Props) => {
|
const { colors } = useTheme()
|
||||||
if (!highlighted) {
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { data } = useStatusHistory({
|
||||||
const { colors } = useTheme()
|
id: status.id,
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
options: { enabled: status.edited_at !== undefined }
|
||||||
|
})
|
||||||
|
|
||||||
const { data } = useStatusHistory({
|
return (
|
||||||
id: status.id,
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||||
options: { enabled: status.edited_at !== undefined }
|
<View style={{ flexDirection: 'row' }}>
|
||||||
})
|
{status.reblogs_count > 0 ? (
|
||||||
|
<CustomText
|
||||||
return (
|
accessibilityLabel={t('shared.actionsUsers.reblogged_by.accessibilityLabel', {
|
||||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
count: status.reblogs_count
|
||||||
<View style={{ flexDirection: 'row' }}>
|
})}
|
||||||
{status.reblogs_count > 0 ? (
|
accessibilityHint={t('shared.actionsUsers.reblogged_by.accessibilityHint')}
|
||||||
<CustomText
|
accessibilityRole='button'
|
||||||
accessibilityLabel={t('shared.actionsUsers.reblogged_by.accessibilityLabel', {
|
style={[styles.text, { color: colors.blue }]}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push('Tab-Shared-Users', {
|
||||||
|
reference: 'statuses',
|
||||||
|
id: status.id,
|
||||||
|
type: 'reblogged_by',
|
||||||
count: status.reblogs_count
|
count: status.reblogs_count
|
||||||
})}
|
})
|
||||||
accessibilityHint={t('shared.actionsUsers.reblogged_by.accessibilityHint')}
|
}
|
||||||
accessibilityRole='button'
|
>
|
||||||
style={[styles.text, { color: colors.blue }]}
|
{t('shared.actionsUsers.reblogged_by.text', {
|
||||||
onPress={() =>
|
count: status.reblogs_count
|
||||||
navigation.push('Tab-Shared-Users', {
|
})}
|
||||||
reference: 'statuses',
|
</CustomText>
|
||||||
id: status.id,
|
) : null}
|
||||||
type: 'reblogged_by',
|
{status.favourites_count > 0 ? (
|
||||||
count: status.reblogs_count
|
<CustomText
|
||||||
})
|
accessibilityLabel={t('shared.actionsUsers.favourited_by.accessibilityLabel', {
|
||||||
}
|
count: status.reblogs_count
|
||||||
>
|
})}
|
||||||
{t('shared.actionsUsers.reblogged_by.text', {
|
accessibilityHint={t('shared.actionsUsers.favourited_by.accessibilityHint')}
|
||||||
count: status.reblogs_count
|
accessibilityRole='button'
|
||||||
})}
|
style={[styles.text, { color: colors.blue }]}
|
||||||
</CustomText>
|
onPress={() =>
|
||||||
) : null}
|
navigation.push('Tab-Shared-Users', {
|
||||||
{status.favourites_count > 0 ? (
|
reference: 'statuses',
|
||||||
<CustomText
|
id: status.id,
|
||||||
accessibilityLabel={t('shared.actionsUsers.favourited_by.accessibilityLabel', {
|
type: 'favourited_by',
|
||||||
count: status.reblogs_count
|
|
||||||
})}
|
|
||||||
accessibilityHint={t('shared.actionsUsers.favourited_by.accessibilityHint')}
|
|
||||||
accessibilityRole='button'
|
|
||||||
style={[styles.text, { color: colors.blue }]}
|
|
||||||
onPress={() =>
|
|
||||||
navigation.push('Tab-Shared-Users', {
|
|
||||||
reference: 'statuses',
|
|
||||||
id: status.id,
|
|
||||||
type: 'favourited_by',
|
|
||||||
count: status.favourites_count
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('shared.actionsUsers.favourited_by.text', {
|
|
||||||
count: status.favourites_count
|
count: status.favourites_count
|
||||||
})}
|
})
|
||||||
</CustomText>
|
}
|
||||||
) : null}
|
>
|
||||||
</View>
|
{t('shared.actionsUsers.favourited_by.text', {
|
||||||
<View>
|
count: status.favourites_count
|
||||||
{data && data.length > 1 ? (
|
})}
|
||||||
<CustomText
|
</CustomText>
|
||||||
accessibilityLabel={t('shared.actionsUsers.history.accessibilityLabel', {
|
) : null}
|
||||||
count: data.length - 1
|
|
||||||
})}
|
|
||||||
accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')}
|
|
||||||
accessibilityRole='button'
|
|
||||||
style={[styles.text, { marginRight: 0, color: colors.blue }]}
|
|
||||||
onPress={() => navigation.push('Tab-Shared-History', { id: status.id })}
|
|
||||||
>
|
|
||||||
{t('shared.actionsUsers.history.text', {
|
|
||||||
count: data.length - 1
|
|
||||||
})}
|
|
||||||
</CustomText>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
<View>
|
||||||
},
|
{data && data.length > 1 ? (
|
||||||
(prev, next) =>
|
<CustomText
|
||||||
prev.status.edited_at === next.status.edited_at &&
|
accessibilityLabel={t('shared.actionsUsers.history.accessibilityLabel', {
|
||||||
prev.status.reblogs_count === next.status.reblogs_count &&
|
count: data.length - 1
|
||||||
prev.status.favourites_count === next.status.favourites_count
|
})}
|
||||||
)
|
accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')}
|
||||||
|
accessibilityRole='button'
|
||||||
|
style={[styles.text, { marginRight: 0, color: colors.blue }]}
|
||||||
|
onPress={() => navigation.push('Tab-Shared-History', { id: status.id })}
|
||||||
|
>
|
||||||
|
{t('shared.actionsUsers.history.text', {
|
||||||
|
count: data.length - 1
|
||||||
|
})}
|
||||||
|
</CustomText>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
text: {
|
text: {
|
||||||
|
@ -1,39 +1,32 @@
|
|||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|
||||||
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 from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineFullConversation = () => {
|
||||||
queryKey?: QueryKeyTimeline
|
const { queryKey, status, disableDetails } = useContext(StatusContext)
|
||||||
status: Mastodon.Status
|
if (!status || disableDetails) return null
|
||||||
|
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
return queryKey &&
|
||||||
|
queryKey[1].page !== 'Toot' &&
|
||||||
|
status.in_reply_to_account_id &&
|
||||||
|
(status.mentions.length === 0 ||
|
||||||
|
status.mentions.filter(mention => mention.id !== status.in_reply_to_account_id).length) ? (
|
||||||
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
style={{
|
||||||
|
color: colors.blue,
|
||||||
|
marginTop: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('shared.fullConversation')}
|
||||||
|
</CustomText>
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineFullConversation = React.memo(
|
|
||||||
({ queryKey, status }: Props) => {
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
return queryKey &&
|
|
||||||
queryKey[1].page !== 'Toot' &&
|
|
||||||
status.in_reply_to_account_id &&
|
|
||||||
(status.mentions.length === 0 ||
|
|
||||||
status.mentions.filter(
|
|
||||||
mention => mention.id !== status.in_reply_to_account_id
|
|
||||||
).length) ? (
|
|
||||||
<CustomText
|
|
||||||
fontStyle='S'
|
|
||||||
style={{
|
|
||||||
color: colors.blue,
|
|
||||||
marginTop: StyleConstants.Spacing.S
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('shared.fullConversation')}
|
|
||||||
</CustomText>
|
|
||||||
) : null
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
export default TimelineFullConversation
|
export default TimelineFullConversation
|
||||||
|
@ -3,21 +3,18 @@ import menuInstance from '@components/contextMenu/instance'
|
|||||||
import menuShare from '@components/contextMenu/share'
|
import menuShare from '@components/contextMenu/share'
|
||||||
import menuStatus from '@components/contextMenu/status'
|
import menuStatus from '@components/contextMenu/status'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|
||||||
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, { useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
import { Platform, View } from 'react-native'
|
import { Platform, View } from 'react-native'
|
||||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineHeaderAndroid: React.FC = () => {
|
||||||
queryKey?: QueryKeyTimeline
|
const { queryKey, rootQueryKey, status, disableDetails, disableOnPress } =
|
||||||
rootQueryKey?: QueryKeyTimeline
|
useContext(StatusContext)
|
||||||
status?: Mastodon.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimelineHeaderAndroid: React.FC<Props> = ({ queryKey, rootQueryKey, status }) => {
|
if (Platform.OS !== 'android' || !status || disableDetails || disableOnPress) return null
|
||||||
if (Platform.OS !== 'android' || !status) return null
|
|
||||||
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
@ -2,46 +2,25 @@ import Icon from '@components/Icon'
|
|||||||
import { displayMessage } from '@components/Message'
|
import { displayMessage } from '@components/Message'
|
||||||
import { ParseEmojis } from '@components/Parse'
|
import { ParseEmojis } from '@components/Parse'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline'
|
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||||
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 from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
import StatusContext from './Context'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
|
|
||||||
const Names = ({ accounts }: { accounts: Mastodon.Account[] }) => {
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomText
|
|
||||||
numberOfLines={1}
|
|
||||||
style={{ ...StyleConstants.FontStyle.M, color: colors.secondary }}
|
|
||||||
>
|
|
||||||
<CustomText>{t('shared.header.conversation.withAccounts')}</CustomText>
|
|
||||||
{accounts.map((account, index) => (
|
|
||||||
<CustomText key={account.id} numberOfLines={1}>
|
|
||||||
{index !== 0 ? t('common:separator') : undefined}
|
|
||||||
<ParseEmojis
|
|
||||||
content={account.display_name || account.username}
|
|
||||||
emojis={account.emojis}
|
|
||||||
fontBold
|
|
||||||
/>
|
|
||||||
</CustomText>
|
|
||||||
))}
|
|
||||||
</CustomText>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
|
||||||
conversation: Mastodon.Conversation
|
conversation: Mastodon.Conversation
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderConversation = ({ queryKey, conversation }: Props) => {
|
const HeaderConversation = ({ conversation }: Props) => {
|
||||||
|
const { queryKey } = useContext(StatusContext)
|
||||||
|
if (!queryKey) return null
|
||||||
|
|
||||||
const { colors, theme } = useTheme()
|
const { colors, theme } = useTheme()
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
@ -70,7 +49,22 @@ const HeaderConversation = ({ queryKey, conversation }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
<View style={{ flex: 3 }}>
|
<View style={{ flex: 3 }}>
|
||||||
<Names accounts={conversation.accounts} />
|
<CustomText
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{ ...StyleConstants.FontStyle.M, color: colors.secondary }}
|
||||||
|
>
|
||||||
|
<CustomText>{t('shared.header.conversation.withAccounts')}</CustomText>
|
||||||
|
{conversation.accounts.map((account, index) => (
|
||||||
|
<CustomText key={account.id} numberOfLines={1}>
|
||||||
|
{index !== 0 ? t('common:separator') : undefined}
|
||||||
|
<ParseEmojis
|
||||||
|
content={account.display_name || account.username}
|
||||||
|
emojis={account.emojis}
|
||||||
|
fontBold
|
||||||
|
/>
|
||||||
|
</CustomText>
|
||||||
|
))}
|
||||||
|
</CustomText>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -3,37 +3,24 @@ import menuInstance from '@components/contextMenu/instance'
|
|||||||
import menuShare from '@components/contextMenu/share'
|
import menuShare from '@components/contextMenu/share'
|
||||||
import menuStatus from '@components/contextMenu/status'
|
import menuStatus from '@components/contextMenu/status'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|
||||||
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, { useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, Pressable, View } from 'react-native'
|
import { Platform, Pressable, View } from 'react-native'
|
||||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||||
|
import StatusContext from './Context'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineHeaderDefault: React.FC = () => {
|
||||||
queryKey?: QueryKeyTimeline
|
const { queryKey, rootQueryKey, status, copiableContent, highlighted, disableDetails } =
|
||||||
rootQueryKey?: QueryKeyTimeline
|
useContext(StatusContext)
|
||||||
status: Mastodon.Status
|
if (!status) return null
|
||||||
highlighted: boolean
|
|
||||||
copiableContent: React.MutableRefObject<{
|
|
||||||
content: string
|
|
||||||
complete: boolean
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimelineHeaderDefault: React.FC<Props> = ({
|
|
||||||
queryKey,
|
|
||||||
rootQueryKey,
|
|
||||||
status,
|
|
||||||
highlighted,
|
|
||||||
copiableContent
|
|
||||||
}) => {
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('componentContextMenu')
|
const { t } = useTranslation('componentContextMenu')
|
||||||
|
|
||||||
@ -76,7 +63,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{Platform.OS !== 'android' && queryKey ? (
|
{Platform.OS !== 'android' && !disableDetails ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityHint={t('accessibilityHint')}
|
accessibilityHint={t('accessibilityHint')}
|
||||||
style={{ flex: 1, alignItems: 'center' }}
|
style={{ flex: 1, alignItems: 'center' }}
|
||||||
|
@ -4,40 +4,41 @@ import menuShare from '@components/contextMenu/share'
|
|||||||
import menuStatus from '@components/contextMenu/status'
|
import menuStatus from '@components/contextMenu/status'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|
||||||
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, { useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
import { Platform, Pressable, View } from 'react-native'
|
import { Platform, Pressable, View } from 'react-native'
|
||||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||||
|
import StatusContext from './Context'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
|
|
||||||
export interface Props {
|
export type Props = {
|
||||||
queryKey: QueryKeyTimeline
|
|
||||||
notification: Mastodon.Notification
|
notification: Mastodon.Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||||
|
const { queryKey, status } = useContext(StatusContext)
|
||||||
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const [openChange, setOpenChange] = useState(false)
|
const [openChange, setOpenChange] = useState(false)
|
||||||
const mShare = menuShare({
|
const mShare = menuShare({
|
||||||
visibility: notification.status?.visibility,
|
visibility: status?.visibility,
|
||||||
type: 'status',
|
type: 'status',
|
||||||
url: notification.status?.url || notification.status?.uri
|
url: status?.url || status?.uri
|
||||||
})
|
})
|
||||||
const mAccount = menuAccount({
|
const mAccount = menuAccount({
|
||||||
type: 'status',
|
type: 'status',
|
||||||
openChange,
|
openChange,
|
||||||
account: notification.status?.account,
|
account: status?.account,
|
||||||
queryKey
|
queryKey
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status: notification.status, queryKey })
|
const mStatus = menuStatus({ status, queryKey })
|
||||||
const mInstance = menuInstance({ status: notification.status, queryKey })
|
const mInstance = menuInstance({ status, queryKey })
|
||||||
|
|
||||||
const actions = () => {
|
const actions = () => {
|
||||||
switch (notification.type) {
|
switch (notification.type) {
|
||||||
@ -46,7 +47,7 @@ const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
|||||||
case 'follow_request':
|
case 'follow_request':
|
||||||
return <RelationshipIncoming id={notification.account.id} />
|
return <RelationshipIncoming id={notification.account.id} />
|
||||||
default:
|
default:
|
||||||
if (notification.status) {
|
if (status) {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{ flex: 1, alignItems: 'center' }}
|
style={{ flex: 1, alignItems: 'center' }}
|
||||||
|
@ -7,39 +7,28 @@ import RelativeTime from '@components/RelativeTime'
|
|||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import {
|
import {
|
||||||
MutationVarsTimelineUpdateStatusProperty,
|
MutationVarsTimelineUpdateStatusProperty,
|
||||||
QueryKeyTimeline,
|
|
||||||
useTimelineMutation
|
useTimelineMutation
|
||||||
} from '@utils/queryHooks/timeline'
|
} from '@utils/queryHooks/timeline'
|
||||||
import updateStatusProperty from '@utils/queryHooks/timeline/updateStatusProperty'
|
import updateStatusProperty from '@utils/queryHooks/timeline/updateStatusProperty'
|
||||||
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 { maxBy } from 'lodash'
|
import { maxBy } from 'lodash'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useContext, useMemo, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelinePoll: React.FC = () => {
|
||||||
queryKey: QueryKeyTimeline
|
const { queryKey, rootQueryKey, status, isReblog, ownAccount, spoilerHidden, disableDetails } =
|
||||||
rootQueryKey?: QueryKeyTimeline
|
useContext(StatusContext)
|
||||||
statusId: Mastodon.Status['id']
|
if (!queryKey || !status || !status.poll) return null
|
||||||
poll: NonNullable<Mastodon.Status['poll']>
|
const poll = status.poll
|
||||||
reblog: boolean
|
|
||||||
sameAccount: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimelinePoll: React.FC<Props> = ({
|
|
||||||
queryKey,
|
|
||||||
rootQueryKey,
|
|
||||||
statusId,
|
|
||||||
poll,
|
|
||||||
reblog,
|
|
||||||
sameAccount
|
|
||||||
}) => {
|
|
||||||
const { colors, theme } = useTheme()
|
const { colors, theme } = useTheme()
|
||||||
const { t, i18n } = useTranslation('componentTimeline')
|
const { t, i18n } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const [allOptions, setAllOptions] = useState(new Array(poll.options.length).fill(false))
|
const [allOptions, setAllOptions] = useState(new Array(status.poll.options.length).fill(false))
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
@ -79,7 +68,7 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
|
|
||||||
const pollButton = useMemo(() => {
|
const pollButton = useMemo(() => {
|
||||||
if (!poll.expired) {
|
if (!poll.expired) {
|
||||||
if (!sameAccount && !poll.voted) {
|
if (!ownAccount && !poll.voted) {
|
||||||
return (
|
return (
|
||||||
<View style={{ marginRight: StyleConstants.Spacing.S }}>
|
<View style={{ marginRight: StyleConstants.Spacing.S }}>
|
||||||
<Button
|
<Button
|
||||||
@ -88,8 +77,8 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
type: 'updateStatusProperty',
|
type: 'updateStatusProperty',
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: statusId,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'poll',
|
property: 'poll',
|
||||||
id: poll.id,
|
id: poll.id,
|
||||||
@ -114,8 +103,8 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
type: 'updateStatusProperty',
|
type: 'updateStatusProperty',
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id: statusId,
|
id: status.id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload: {
|
payload: {
|
||||||
property: 'poll',
|
property: 'poll',
|
||||||
id: poll.id,
|
id: poll.id,
|
||||||
@ -258,6 +247,8 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spoilerHidden || disableDetails) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
||||||
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
||||||
|
@ -5,131 +5,119 @@ import { useTranslateQuery } from '@utils/queryHooks/translate'
|
|||||||
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 * as Localization from 'expo-localization'
|
import * as Localization from 'expo-localization'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable } from 'react-native'
|
import { Pressable } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
import detectLanguage from 'react-native-language-detection'
|
import detectLanguage from 'react-native-language-detection'
|
||||||
|
import StatusContext from './Context'
|
||||||
|
|
||||||
export interface Props {
|
const TimelineTranslate = () => {
|
||||||
highlighted: boolean
|
const { status, highlighted } = useContext(StatusContext)
|
||||||
status: Pick<Mastodon.Status, 'language' | 'spoiler_text' | 'content' | 'emojis'>
|
if (!status || !highlighted) return null
|
||||||
}
|
|
||||||
|
|
||||||
const TimelineTranslate = React.memo(
|
const { t } = useTranslation('componentTimeline')
|
||||||
({ highlighted, status }: Props) => {
|
const { colors } = useTheme()
|
||||||
if (!highlighted) {
|
|
||||||
return null
|
const text = status.spoiler_text ? [status.spoiler_text, status.content] : [status.content]
|
||||||
|
|
||||||
|
for (const i in text) {
|
||||||
|
for (const emoji of status.emojis) {
|
||||||
|
text[i] = text[i].replaceAll(`:${emoji.shortcode}:`, ' ')
|
||||||
}
|
}
|
||||||
|
text[i] = text[i]
|
||||||
|
.replace(/(<([^>]+)>)/gi, ' ')
|
||||||
|
.replace(/@.*? /gi, ' ')
|
||||||
|
.replace(/#.*? /gi, ' ')
|
||||||
|
.replace(/http(s):\/\/.*? /gi, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
const [detectedLanguage, setDetectedLanguage] = useState<string>('')
|
||||||
const { colors } = useTheme()
|
useEffect(() => {
|
||||||
|
const detect = async () => {
|
||||||
const text = status.spoiler_text ? [status.spoiler_text, status.content] : [status.content]
|
const result = await detectLanguage(text.join(`\n\n`)).catch(() => {
|
||||||
|
// No need to log language detection failure
|
||||||
for (const i in text) {
|
})
|
||||||
for (const emoji of status.emojis) {
|
result?.detected && setDetectedLanguage(result.detected.slice(0, 2))
|
||||||
text[i] = text[i].replaceAll(`:${emoji.shortcode}:`, ' ')
|
|
||||||
}
|
|
||||||
text[i] = text[i]
|
|
||||||
.replace(/(<([^>]+)>)/gi, ' ')
|
|
||||||
.replace(/@.*? /gi, ' ')
|
|
||||||
.replace(/#.*? /gi, ' ')
|
|
||||||
.replace(/http(s):\/\/.*? /gi, ' ')
|
|
||||||
}
|
}
|
||||||
|
detect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
const [detectedLanguage, setDetectedLanguage] = useState<string>('')
|
const settingsLanguage = getLanguage()
|
||||||
useEffect(() => {
|
const targetLanguage = settingsLanguage?.startsWith('en')
|
||||||
const detect = async () => {
|
? Localization.locale || settingsLanguage || 'en'
|
||||||
const result = await detectLanguage(text.join(`\n\n`)).catch(() => {
|
: settingsLanguage || Localization.locale || 'en'
|
||||||
// No need to log language detection failure
|
|
||||||
})
|
|
||||||
result?.detected && setDetectedLanguage(result.detected.slice(0, 2))
|
|
||||||
}
|
|
||||||
detect()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const settingsLanguage = getLanguage()
|
const [enabled, setEnabled] = useState(false)
|
||||||
const targetLanguage = settingsLanguage?.startsWith('en')
|
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
||||||
? Localization.locale || settingsLanguage || 'en'
|
source: detectedLanguage,
|
||||||
: settingsLanguage || Localization.locale || 'en'
|
target: targetLanguage,
|
||||||
|
text,
|
||||||
|
options: { enabled }
|
||||||
|
})
|
||||||
|
|
||||||
const [enabled, setEnabled] = useState(false)
|
if (!detectedLanguage) {
|
||||||
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
return null
|
||||||
source: detectedLanguage,
|
}
|
||||||
target: targetLanguage,
|
if (Localization.locale.slice(0, 2).includes(detectedLanguage)) {
|
||||||
text,
|
return null
|
||||||
options: { enabled }
|
}
|
||||||
})
|
if (settingsLanguage?.slice(0, 2).includes(detectedLanguage)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (!detectedLanguage) {
|
return (
|
||||||
return null
|
<>
|
||||||
}
|
<Pressable
|
||||||
if (Localization.locale.slice(0, 2).includes(detectedLanguage)) {
|
style={{
|
||||||
return null
|
flexDirection: 'row',
|
||||||
}
|
alignItems: 'center',
|
||||||
if (settingsLanguage?.slice(0, 2).includes(detectedLanguage)) {
|
paddingVertical: StyleConstants.Spacing.S,
|
||||||
return null
|
paddingBottom: isSuccess ? 0 : undefined
|
||||||
}
|
}}
|
||||||
|
onPress={() => {
|
||||||
return (
|
if (enabled) {
|
||||||
<>
|
if (!isSuccess) {
|
||||||
<Pressable
|
refetch()
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: StyleConstants.Spacing.S,
|
|
||||||
paddingBottom: isSuccess ? 0 : undefined
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
if (enabled) {
|
|
||||||
if (!isSuccess) {
|
|
||||||
refetch()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setEnabled(true)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setEnabled(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='M'
|
||||||
|
style={{
|
||||||
|
color: isLoading || isSuccess ? colors.secondary : isError ? colors.red : colors.blue
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomText
|
{isError
|
||||||
fontStyle='M'
|
? t('shared.translate.failed')
|
||||||
style={{
|
: isSuccess
|
||||||
color: isLoading || isSuccess ? colors.secondary : isError ? colors.red : colors.blue
|
? typeof data?.error === 'string'
|
||||||
}}
|
? t(`shared.translate.${data.error}`)
|
||||||
>
|
: t('shared.translate.succeed', {
|
||||||
{isError
|
provider: data?.provider,
|
||||||
? t('shared.translate.failed')
|
source: data?.sourceLanguage
|
||||||
: isSuccess
|
})
|
||||||
? typeof data?.error === 'string'
|
: t('shared.translate.default')}
|
||||||
? t(`shared.translate.${data.error}`)
|
</CustomText>
|
||||||
: t('shared.translate.succeed', {
|
<CustomText>
|
||||||
provider: data?.provider,
|
{__DEV__ ? ` Source: ${detectedLanguage}; Target: ${targetLanguage}` : undefined}
|
||||||
source: data?.sourceLanguage
|
</CustomText>
|
||||||
})
|
{isLoading ? (
|
||||||
: t('shared.translate.default')}
|
<Circle
|
||||||
</CustomText>
|
size={StyleConstants.Font.Size.M}
|
||||||
<CustomText>
|
color={colors.disabled}
|
||||||
{__DEV__ ? ` Source: ${detectedLanguage}; Target: ${targetLanguage}` : undefined}
|
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||||
</CustomText>
|
/>
|
||||||
{isLoading ? (
|
) : null}
|
||||||
<Circle
|
</Pressable>
|
||||||
size={StyleConstants.Font.Size.M}
|
{data && data.error === undefined
|
||||||
color={colors.disabled}
|
? data.text.map((d, i) => <ParseHTML key={i} content={d} size={'M'} numberOfLines={999} />)
|
||||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
: null}
|
||||||
/>
|
</>
|
||||||
) : null}
|
)
|
||||||
</Pressable>
|
}
|
||||||
{data && data.error === undefined
|
|
||||||
? data.text.map((d, i) => (
|
|
||||||
<ParseHTML key={i} content={d} size={'M'} numberOfLines={999} />
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(prev, next) =>
|
|
||||||
prev.status.content === next.status.content &&
|
|
||||||
prev.status.spoiler_text === next.status.spoiler_text
|
|
||||||
)
|
|
||||||
|
|
||||||
export default TimelineTranslate
|
export default TimelineTranslate
|
||||||
|
@ -2,10 +2,7 @@ import apiInstance, { InstanceResponse } from '@api/instance'
|
|||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import queryClient from '@helpers/queryClient'
|
import queryClient from '@helpers/queryClient'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
import {
|
import { checkInstanceFeature, getInstanceNotificationsFilter } from '@utils/slices/instancesSlice'
|
||||||
checkInstanceFeature,
|
|
||||||
getInstanceNotificationsFilter
|
|
||||||
} from '@utils/slices/instancesSlice'
|
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
import { uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
import {
|
import {
|
||||||
@ -30,10 +27,7 @@ export type QueryKeyTimeline = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const queryFunction = async ({
|
const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyTimeline>) => {
|
||||||
queryKey,
|
|
||||||
pageParam
|
|
||||||
}: QueryFunctionContext<QueryKeyTimeline>) => {
|
|
||||||
const { page, account, hashtag, list, toot } = queryKey[1]
|
const { page, account, hashtag, list, toot } = queryKey[1]
|
||||||
let params: { [key: string]: string } = { ...pageParam }
|
let params: { [key: string]: string } = { ...pageParam }
|
||||||
|
|
||||||
@ -65,9 +59,9 @@ const queryFunction = async ({
|
|||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
const rootStore = store.getState()
|
const rootStore = store.getState()
|
||||||
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
|
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
|
||||||
const usePositiveFilter = checkInstanceFeature(
|
const usePositiveFilter = checkInstanceFeature('notification_types_positive_filter')(
|
||||||
'notification_types_positive_filter'
|
rootStore
|
||||||
)(rootStore)
|
)
|
||||||
return apiInstance<Mastodon.Notification[]>({
|
return apiInstance<Mastodon.Notification[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'notifications',
|
url: 'notifications',
|
||||||
@ -99,9 +93,7 @@ const queryFunction = async ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const res1 = await apiInstance<
|
const res1 = await apiInstance<(Mastodon.Status & { _pinned: boolean })[]>({
|
||||||
(Mastodon.Status & { _pinned: boolean })[]
|
|
||||||
>({
|
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `accounts/${account}/statuses`,
|
url: `accounts/${account}/statuses`,
|
||||||
params: {
|
params: {
|
||||||
@ -190,11 +182,7 @@ const queryFunction = async ({
|
|||||||
url: `statuses/${toot}/context`
|
url: `statuses/${toot}/context`
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
body: [
|
body: [...res2_1.body.ancestors, res1_1.body, ...res2_1.body.descendants]
|
||||||
...res2_1.body.ancestors,
|
|
||||||
res1_1.body,
|
|
||||||
...res2_1.body.descendants
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return Promise.reject()
|
return Promise.reject()
|
||||||
@ -207,10 +195,7 @@ const useTimelineQuery = ({
|
|||||||
options,
|
options,
|
||||||
...queryKeyParams
|
...queryKeyParams
|
||||||
}: QueryKeyTimeline[1] & {
|
}: QueryKeyTimeline[1] & {
|
||||||
options?: UseInfiniteQueryOptions<
|
options?: UseInfiniteQueryOptions<InstanceResponse<Mastodon.Status[]>, AxiosError>
|
||||||
InstanceResponse<Mastodon.Status[]>,
|
|
||||||
AxiosError
|
|
||||||
>
|
|
||||||
}) => {
|
}) => {
|
||||||
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
|
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
|
||||||
return useInfiniteQuery(queryKey, queryFunction, {
|
return useInfiniteQuery(queryKey, queryFunction, {
|
||||||
@ -284,7 +269,7 @@ export type MutationVarsTimelineUpdateStatusProperty = {
|
|||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
rootQueryKey?: QueryKeyTimeline
|
rootQueryKey?: QueryKeyTimeline
|
||||||
id: Mastodon.Status['id'] | Mastodon.Poll['id']
|
id: Mastodon.Status['id'] | Mastodon.Poll['id']
|
||||||
reblog?: boolean
|
isReblog?: boolean
|
||||||
payload:
|
payload:
|
||||||
| {
|
| {
|
||||||
property: 'bookmarked' | 'muted' | 'pinned'
|
property: 'bookmarked' | 'muted' | 'pinned'
|
||||||
@ -384,9 +369,9 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
|||||||
}
|
}
|
||||||
return apiInstance<Mastodon.Status>({
|
return apiInstance<Mastodon.Status>({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `statuses/${params.id}/${
|
url: `statuses/${params.id}/${params.payload.currentValue ? 'un' : ''}${
|
||||||
params.payload.currentValue ? 'un' : ''
|
MapPropertyToUrl[params.payload.property]
|
||||||
}${MapPropertyToUrl[params.payload.property]}`,
|
}`,
|
||||||
...(params.payload.property === 'reblogged' && { body })
|
...(params.payload.property === 'reblogged' && { body })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -396,9 +381,9 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
|||||||
case 'mute':
|
case 'mute':
|
||||||
return apiInstance<Mastodon.Account>({
|
return apiInstance<Mastodon.Account>({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `accounts/${params.id}/${
|
url: `accounts/${params.id}/${params.payload.currentValue ? 'un' : ''}${
|
||||||
params.payload.currentValue ? 'un' : ''
|
params.payload.property
|
||||||
}${params.payload.property}`
|
}`
|
||||||
})
|
})
|
||||||
case 'reports':
|
case 'reports':
|
||||||
return apiInstance<Mastodon.Account>({
|
return apiInstance<Mastodon.Account>({
|
||||||
@ -455,8 +440,7 @@ const useTimelineMutation = ({
|
|||||||
...(onMutate && {
|
...(onMutate && {
|
||||||
onMutate: params => {
|
onMutate: params => {
|
||||||
queryClient.cancelQueries(params.queryKey)
|
queryClient.cancelQueries(params.queryKey)
|
||||||
const oldData =
|
const oldData = params.queryKey && queryClient.getQueryData(params.queryKey)
|
||||||
params.queryKey && queryClient.getQueryData(params.queryKey)
|
|
||||||
|
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
switch (params.type) {
|
switch (params.type) {
|
||||||
|
@ -2,32 +2,27 @@ import { MutationVarsTimelineUpdateStatusProperty } from '@utils/queryHooks/time
|
|||||||
|
|
||||||
const updateStatus = ({
|
const updateStatus = ({
|
||||||
item,
|
item,
|
||||||
reblog,
|
isReblog,
|
||||||
payload
|
payload
|
||||||
}: {
|
}: {
|
||||||
item: Mastodon.Status
|
item: Mastodon.Status
|
||||||
reblog?: boolean
|
isReblog?: boolean
|
||||||
payload: MutationVarsTimelineUpdateStatusProperty['payload']
|
payload: MutationVarsTimelineUpdateStatusProperty['payload']
|
||||||
}) => {
|
}) => {
|
||||||
switch (payload.property) {
|
switch (payload.property) {
|
||||||
case 'poll':
|
case 'poll':
|
||||||
if (reblog) {
|
if (isReblog) {
|
||||||
item.reblog!.poll = payload.data
|
item.reblog!.poll = payload.data
|
||||||
} else {
|
} else {
|
||||||
item.poll = payload.data
|
item.poll = payload.data
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (reblog) {
|
if (isReblog) {
|
||||||
item.reblog![payload.property] =
|
item.reblog![payload.property] =
|
||||||
typeof payload.currentValue === 'boolean'
|
typeof payload.currentValue === 'boolean' ? !payload.currentValue : true
|
||||||
? !payload.currentValue
|
|
||||||
: true
|
|
||||||
if (payload.propertyCount) {
|
if (payload.propertyCount) {
|
||||||
if (
|
if (typeof payload.currentValue === 'boolean' && payload.currentValue) {
|
||||||
typeof payload.currentValue === 'boolean' &&
|
|
||||||
payload.currentValue
|
|
||||||
) {
|
|
||||||
item.reblog![payload.propertyCount] = payload.countValue - 1
|
item.reblog![payload.propertyCount] = payload.countValue - 1
|
||||||
} else {
|
} else {
|
||||||
item.reblog![payload.propertyCount] = payload.countValue + 1
|
item.reblog![payload.propertyCount] = payload.countValue + 1
|
||||||
@ -35,14 +30,9 @@ const updateStatus = ({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item[payload.property] =
|
item[payload.property] =
|
||||||
typeof payload.currentValue === 'boolean'
|
typeof payload.currentValue === 'boolean' ? !payload.currentValue : true
|
||||||
? !payload.currentValue
|
|
||||||
: true
|
|
||||||
if (payload.propertyCount) {
|
if (payload.propertyCount) {
|
||||||
if (
|
if (typeof payload.currentValue === 'boolean' && payload.currentValue) {
|
||||||
typeof payload.currentValue === 'boolean' &&
|
|
||||||
payload.currentValue
|
|
||||||
) {
|
|
||||||
item[payload.propertyCount] = payload.countValue - 1
|
item[payload.propertyCount] = payload.countValue - 1
|
||||||
} else {
|
} else {
|
||||||
item[payload.propertyCount] = payload.countValue + 1
|
item[payload.propertyCount] = payload.countValue + 1
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import queryClient from '@helpers/queryClient'
|
import queryClient from '@helpers/queryClient'
|
||||||
import { InfiniteData } from 'react-query'
|
import { InfiniteData } from 'react-query'
|
||||||
import {
|
import { MutationVarsTimelineUpdateStatusProperty, TimelineData } from '../timeline'
|
||||||
MutationVarsTimelineUpdateStatusProperty,
|
|
||||||
TimelineData
|
|
||||||
} from '../timeline'
|
|
||||||
import updateConversation from './update/conversation'
|
import updateConversation from './update/conversation'
|
||||||
import updateNotification from './update/notification'
|
import updateNotification from './update/notification'
|
||||||
import updateStatus from './update/status'
|
import updateStatus from './update/status'
|
||||||
@ -12,12 +9,54 @@ const updateStatusProperty = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
id,
|
id,
|
||||||
reblog,
|
isReblog,
|
||||||
payload
|
payload
|
||||||
}: MutationVarsTimelineUpdateStatusProperty) => {
|
}: MutationVarsTimelineUpdateStatusProperty) => {
|
||||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(queryKey, old => {
|
||||||
queryKey,
|
if (old) {
|
||||||
old => {
|
let foundToot = false
|
||||||
|
old.pages = old.pages.map(page => {
|
||||||
|
// Skip rest of the pages if any toot is found
|
||||||
|
if (foundToot) {
|
||||||
|
return page
|
||||||
|
} else {
|
||||||
|
if (typeof (page.body as Mastodon.Conversation[])[0].unread === 'boolean') {
|
||||||
|
const items = page.body as Mastodon.Conversation[]
|
||||||
|
const tootIndex = items.findIndex(({ last_status }) => last_status?.id === id)
|
||||||
|
if (tootIndex >= 0) {
|
||||||
|
foundToot = true
|
||||||
|
updateConversation({ item: items[tootIndex], payload })
|
||||||
|
}
|
||||||
|
return page
|
||||||
|
} else if (typeof (page.body as Mastodon.Notification[])[0].type === 'string') {
|
||||||
|
const items = page.body as Mastodon.Notification[]
|
||||||
|
const tootIndex = items.findIndex(({ status }) => status?.id === id)
|
||||||
|
if (tootIndex >= 0) {
|
||||||
|
foundToot = true
|
||||||
|
updateNotification({ item: items[tootIndex], payload })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const items = page.body as Mastodon.Status[]
|
||||||
|
const tootIndex = isReblog
|
||||||
|
? items.findIndex(({ reblog }) => reblog?.id === id)
|
||||||
|
: items.findIndex(toot => toot.id === id)
|
||||||
|
// if favourites page and notifications page, remove the item instead
|
||||||
|
if (tootIndex >= 0) {
|
||||||
|
foundToot = true
|
||||||
|
updateStatus({ item: items[tootIndex], isReblog, payload })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return old
|
||||||
|
})
|
||||||
|
|
||||||
|
rootQueryKey &&
|
||||||
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(rootQueryKey, old => {
|
||||||
if (old) {
|
if (old) {
|
||||||
let foundToot = false
|
let foundToot = false
|
||||||
old.pages = old.pages.map(page => {
|
old.pages = old.pages.map(page => {
|
||||||
@ -25,39 +64,30 @@ const updateStatusProperty = ({
|
|||||||
if (foundToot) {
|
if (foundToot) {
|
||||||
return page
|
return page
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (typeof (page.body as Mastodon.Conversation[])[0].unread === 'boolean') {
|
||||||
typeof (page.body as Mastodon.Conversation[])[0].unread ===
|
|
||||||
'boolean'
|
|
||||||
) {
|
|
||||||
const items = page.body as Mastodon.Conversation[]
|
const items = page.body as Mastodon.Conversation[]
|
||||||
const tootIndex = items.findIndex(
|
const tootIndex = items.findIndex(({ last_status }) => last_status?.id === id)
|
||||||
({ last_status }) => last_status?.id === id
|
|
||||||
)
|
|
||||||
if (tootIndex >= 0) {
|
if (tootIndex >= 0) {
|
||||||
foundToot = true
|
foundToot = true
|
||||||
updateConversation({ item: items[tootIndex], payload })
|
updateConversation({ item: items[tootIndex], payload })
|
||||||
}
|
}
|
||||||
return page
|
return page
|
||||||
} else if (
|
} else if (typeof (page.body as Mastodon.Notification[])[0].type === 'string') {
|
||||||
typeof (page.body as Mastodon.Notification[])[0].type === 'string'
|
|
||||||
) {
|
|
||||||
const items = page.body as Mastodon.Notification[]
|
const items = page.body as Mastodon.Notification[]
|
||||||
const tootIndex = items.findIndex(
|
const tootIndex = items.findIndex(({ status }) => status?.id === id)
|
||||||
({ status }) => status?.id === id
|
|
||||||
)
|
|
||||||
if (tootIndex >= 0) {
|
if (tootIndex >= 0) {
|
||||||
foundToot = true
|
foundToot = true
|
||||||
updateNotification({ item: items[tootIndex], payload })
|
updateNotification({ item: items[tootIndex], payload })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const items = page.body as Mastodon.Status[]
|
const items = page.body as Mastodon.Status[]
|
||||||
const tootIndex = reblog
|
const tootIndex = isReblog
|
||||||
? items.findIndex(({ reblog }) => reblog?.id === id)
|
? items.findIndex(({ reblog }) => reblog?.id === id)
|
||||||
: items.findIndex(toot => toot.id === id)
|
: items.findIndex(toot => toot.id === id)
|
||||||
// if favourites page and notifications page, remove the item instead
|
// if favourites page and notifications page, remove the item instead
|
||||||
if (tootIndex >= 0) {
|
if (tootIndex >= 0) {
|
||||||
foundToot = true
|
foundToot = true
|
||||||
updateStatus({ item: items[tootIndex], reblog, payload })
|
updateStatus({ item: items[tootIndex], isReblog, payload })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,65 +97,7 @@ const updateStatusProperty = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return old
|
return old
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
rootQueryKey &&
|
|
||||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
|
||||||
rootQueryKey,
|
|
||||||
old => {
|
|
||||||
if (old) {
|
|
||||||
let foundToot = false
|
|
||||||
old.pages = old.pages.map(page => {
|
|
||||||
// Skip rest of the pages if any toot is found
|
|
||||||
if (foundToot) {
|
|
||||||
return page
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
typeof (page.body as Mastodon.Conversation[])[0].unread ===
|
|
||||||
'boolean'
|
|
||||||
) {
|
|
||||||
const items = page.body as Mastodon.Conversation[]
|
|
||||||
const tootIndex = items.findIndex(
|
|
||||||
({ last_status }) => last_status?.id === id
|
|
||||||
)
|
|
||||||
if (tootIndex >= 0) {
|
|
||||||
foundToot = true
|
|
||||||
updateConversation({ item: items[tootIndex], payload })
|
|
||||||
}
|
|
||||||
return page
|
|
||||||
} else if (
|
|
||||||
typeof (page.body as Mastodon.Notification[])[0].type ===
|
|
||||||
'string'
|
|
||||||
) {
|
|
||||||
const items = page.body as Mastodon.Notification[]
|
|
||||||
const tootIndex = items.findIndex(
|
|
||||||
({ status }) => status?.id === id
|
|
||||||
)
|
|
||||||
if (tootIndex >= 0) {
|
|
||||||
foundToot = true
|
|
||||||
updateNotification({ item: items[tootIndex], payload })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const items = page.body as Mastodon.Status[]
|
|
||||||
const tootIndex = reblog
|
|
||||||
? items.findIndex(({ reblog }) => reblog?.id === id)
|
|
||||||
: items.findIndex(toot => toot.id === id)
|
|
||||||
// if favourites page and notifications page, remove the item instead
|
|
||||||
if (tootIndex >= 0) {
|
|
||||||
foundToot = true
|
|
||||||
updateStatus({ item: items[tootIndex], reblog, payload })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return page
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return old
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default updateStatusProperty
|
export default updateStatusProperty
|
||||||
|
Loading…
x
Reference in New Issue
Block a user