mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Fix status interactions
This commit is contained in:
@ -1,11 +1,12 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import { RootStackParamList, useNavState } from '@utils/navigation/navigators'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu[][] => {
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
const navigationState = useNavState()
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
@ -17,7 +18,8 @@ const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu[][] =>
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'conversation',
|
||||
accts: [account.acct],
|
||||
visibility: 'direct'
|
||||
visibility: 'direct',
|
||||
navigationState
|
||||
}),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
@ -33,7 +35,8 @@ const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu[][] =>
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'conversation',
|
||||
accts: [account.acct],
|
||||
visibility: 'public'
|
||||
visibility: 'public',
|
||||
navigationState
|
||||
}),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
|
@ -7,7 +7,7 @@ import ComposeRoot from '@screens/Compose/Root'
|
||||
import { formatText } from '@screens/Compose/utils/processText'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { handleError } from '@utils/api/helpers'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { RootStackScreenProps, useNavState } from '@utils/navigation/navigators'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { searchLocalStatus } from '@utils/queryHooks/search'
|
||||
@ -347,13 +347,19 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
|
||||
switch (params?.type) {
|
||||
case undefined:
|
||||
case 'conversation':
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['Timeline', { page: 'Following' }],
|
||||
exact: false
|
||||
})
|
||||
break
|
||||
case 'conversation':
|
||||
case 'edit': // doesn't work
|
||||
// mutateTimeline.mutate({
|
||||
// type: 'editItem',
|
||||
// status: res,
|
||||
// navigationState: params.navigationState
|
||||
// })
|
||||
// break
|
||||
case 'deleteEdit':
|
||||
case 'reply':
|
||||
for (const navState of params.navigationState) {
|
||||
|
@ -8,6 +8,7 @@ import apiGeneral from '@utils/api/general'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -65,18 +66,15 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
||||
})
|
||||
navigation.setParams({ toot, queryKey: toot._remote ? queryKey.remote : queryKey.local })
|
||||
navigation.setParams({ toot, queryKey: queryKey.local })
|
||||
}, [hasRemoteContent])
|
||||
|
||||
const flRef = useRef<FlatList>(null)
|
||||
const scrolled = useRef(false)
|
||||
|
||||
const match = urlMatcher(toot.url || toot.uri)
|
||||
const finalData = useRef<(Mastodon.Status & { key?: string })[]>([
|
||||
{ ...toot, _level: 0, key: 'cached' }
|
||||
])
|
||||
const highlightIndex = useRef<number>(0)
|
||||
const queryLocal = useQuery(
|
||||
const query = useQuery<{ pages: { body: (Mastodon.Status & { _key?: 'cached' })[] }[] }>(
|
||||
queryKey.local,
|
||||
async () => {
|
||||
const context = await apiInstance<{
|
||||
@ -85,30 +83,30 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
}>({
|
||||
method: 'get',
|
||||
url: `statuses/${toot.id}/context`
|
||||
})
|
||||
}).then(res => res.body)
|
||||
|
||||
const statuses: (Mastodon.Status & { _level?: number })[] = [
|
||||
...context.body.ancestors,
|
||||
{ ...toot },
|
||||
...context.body.descendants
|
||||
]
|
||||
highlightIndex.current = context.ancestors.length
|
||||
|
||||
const highlight = context.body.ancestors.length
|
||||
highlightIndex.current = highlight
|
||||
const statuses = [...context.ancestors, { ...toot }, ...context.descendants]
|
||||
|
||||
for (const [index, status] of statuses.entries()) {
|
||||
if (index < highlight || status.id === toot.id) {
|
||||
statuses[index]._level = 0
|
||||
continue
|
||||
}
|
||||
|
||||
const repliedLevel = statuses.find(s => s.id === status.in_reply_to_id)?._level
|
||||
statuses[index]._level = (repliedLevel || 0) + 1
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
body: statuses.map((status, index) => {
|
||||
if (index < highlightIndex.current || status.id === toot.id) {
|
||||
return { ...status, _level: 0 }
|
||||
} else {
|
||||
const repliedLevel: number =
|
||||
statuses.find(s => s.id === status.in_reply_to_id)?._level || 0
|
||||
return { ...status, _level: repliedLevel + 1 }
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return { pages: [{ body: statuses }] }
|
||||
},
|
||||
{
|
||||
initialData: { pages: [{ body: [{ ...toot, _level: 0, _key: 'cached' }] }] },
|
||||
enabled: !toot._remote,
|
||||
staleTime: 0,
|
||||
refetchOnMount: true,
|
||||
@ -118,33 +116,29 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (finalData.current[0].key === 'cached') {
|
||||
finalData.current = data.pages[0].body
|
||||
|
||||
if (!scrolled.current) {
|
||||
scrolled.current = true
|
||||
const pointer = data.pages[0].body.findIndex(({ id }) => id === toot.id)
|
||||
if (pointer < 1) return
|
||||
const length = flRef.current?.props.data?.length
|
||||
if (!length) return
|
||||
try {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
flRef.current?.scrollToIndex({
|
||||
index: pointer,
|
||||
viewOffset: 100
|
||||
})
|
||||
} catch {}
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
if (!scrolled.current) {
|
||||
scrolled.current = true
|
||||
const pointer = data.pages[0].body.findIndex(({ id }) => id === toot.id)
|
||||
if (pointer < 1) return
|
||||
const length = flRef.current?.props.data?.length
|
||||
if (!length) return
|
||||
try {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
flRef.current?.scrollToIndex({
|
||||
index: pointer,
|
||||
viewOffset: 100
|
||||
})
|
||||
} catch {}
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
useQuery(
|
||||
useQuery<Mastodon.Status[]>(
|
||||
queryKey.remote,
|
||||
async () => {
|
||||
const domain = match?.domain
|
||||
@ -165,30 +159,22 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
url: `api/v1/statuses/${id}/context`
|
||||
}).then(res => res.body)
|
||||
|
||||
if (!context) {
|
||||
return Promise.reject('Cannot retrieve remote context')
|
||||
if (!context?.ancestors.length && !context?.descendants.length) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
const statuses: (Mastodon.Status & { _level?: number })[] = [
|
||||
...context.ancestors,
|
||||
{ ...toot },
|
||||
...context.descendants
|
||||
]
|
||||
highlightIndex.current = context.ancestors.length
|
||||
|
||||
const highlight = context.ancestors.length
|
||||
highlightIndex.current = highlight
|
||||
const statuses = [...context.ancestors, { ...toot }, ...context.descendants]
|
||||
|
||||
for (const [index, status] of statuses.entries()) {
|
||||
if (index < highlight || status.id === toot.id) {
|
||||
statuses[index]._level = 0
|
||||
continue
|
||||
return statuses.map((status, index) => {
|
||||
if (index < highlightIndex.current || status.id === toot.id) {
|
||||
return { ...status, _level: 0 }
|
||||
}
|
||||
|
||||
const repliedLevel = statuses.find(s => s.id === status.in_reply_to_id)?._level
|
||||
statuses[index]._level = (repliedLevel || 0) + 1
|
||||
}
|
||||
|
||||
return { pages: [{ body: statuses }] }
|
||||
const repliedLevel: number = statuses.find(s => s.id === status.in_reply_to_id)?._level || 0
|
||||
return { ...status, _level: repliedLevel + 1 }
|
||||
})
|
||||
},
|
||||
{
|
||||
enabled:
|
||||
@ -197,39 +183,57 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
staleTime: 0,
|
||||
refetchOnMount: true,
|
||||
onSuccess: data => {
|
||||
if (finalData.current.length < 1 && data.pages[0].body.length < 1) {
|
||||
if (query.data.pages[0].body.length < 1 && data.length < 1) {
|
||||
navigation.goBack()
|
||||
return
|
||||
}
|
||||
|
||||
if (finalData.current.length < data.pages[0].body.length) {
|
||||
finalData.current = data.pages[0].body.map(remote => {
|
||||
const localMatch = finalData.current.find(local => local.uri === remote.uri)
|
||||
if (localMatch) {
|
||||
delete localMatch.key
|
||||
return localMatch
|
||||
} else {
|
||||
remote._remote = true
|
||||
if (query.data.pages[0].body.length < data.length) {
|
||||
queryClient.cancelQueries(queryKey.local)
|
||||
queryClient.setQueryData<{
|
||||
pages: { body: Mastodon.Status[] }[]
|
||||
}>(queryKey.local, old => {
|
||||
if (!old) return old
|
||||
|
||||
remote.account._remote = true
|
||||
remote.mentions = remote.mentions.map(mention => ({ ...mention, _remote: true }))
|
||||
if (remote.reblog) {
|
||||
remote.reblog.account._remote = true
|
||||
remote.reblog.mentions = remote.mentions.map(mention => ({
|
||||
...mention,
|
||||
_remote: true
|
||||
}))
|
||||
}
|
||||
|
||||
return remote
|
||||
setHasRemoteContent(true)
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
body: data.map(remote => {
|
||||
const localMatch = query.data.pages[0].body.find(
|
||||
local => local.uri === remote.uri
|
||||
)
|
||||
if (localMatch) {
|
||||
delete localMatch._key
|
||||
return localMatch
|
||||
} else {
|
||||
return {
|
||||
...remote,
|
||||
_remote: true,
|
||||
account: { ...remote.account, _remote: true },
|
||||
mentions: remote.mentions.map(mention => ({ ...mention, _remote: true })),
|
||||
...(remote.reblog && {
|
||||
reblog: {
|
||||
...remote.reblog,
|
||||
_remote: true,
|
||||
account: { ...remote.reblog.account, _remote: true },
|
||||
mentions: remote.reblog.mentions.map(mention => ({
|
||||
...mention,
|
||||
_remote: true
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
setHasRemoteContent(true)
|
||||
}
|
||||
|
||||
scrolled.current = true
|
||||
const pointer = data.pages[0].body.findIndex(({ id }) => id === toot.id)
|
||||
const pointer = data.findIndex(({ id }) => id === toot.id)
|
||||
if (pointer < 1) return
|
||||
const length = flRef.current?.props.data?.length
|
||||
if (!length) return
|
||||
@ -258,11 +262,11 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
ref={flRef}
|
||||
scrollEventThrottle={16}
|
||||
windowSize={7}
|
||||
data={finalData.current}
|
||||
data={query.data.pages?.[0].body}
|
||||
renderItem={({ item, index }) => {
|
||||
const prev = finalData.current[index - 1]?._level || 0
|
||||
const prev = query.data.pages[0].body[index - 1]?._level || 0
|
||||
const curr = item._level
|
||||
const next = finalData.current[index + 1]?._level || 0
|
||||
const next = query.data.pages[0].body[index + 1]?._level || 0
|
||||
|
||||
return (
|
||||
<View
|
||||
@ -416,7 +420,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
const offset = error.averageItemLength * error.index
|
||||
flRef.current?.scrollToOffset({ offset })
|
||||
try {
|
||||
error.index < finalData.current.length &&
|
||||
error.index < query.data.pages[0].body.length &&
|
||||
setTimeout(
|
||||
() =>
|
||||
flRef.current?.scrollToIndex({
|
||||
@ -436,7 +440,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
marginHorizontal: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{queryLocal.isFetching ? (
|
||||
{query.isFetching ? (
|
||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
||||
) : null}
|
||||
</View>
|
||||
|
@ -41,6 +41,7 @@ export type RootStackParamList = {
|
||||
accts: Mastodon.Account['acct'][]
|
||||
visibility: ComposeState['visibility']
|
||||
text?: string // For contacting tooot only
|
||||
navigationState: (QueryKeyTimeline | undefined)[]
|
||||
}
|
||||
| {
|
||||
type: 'share'
|
||||
|
@ -16,6 +16,7 @@ import { AxiosError } from 'axios'
|
||||
import { uniqBy } from 'lodash'
|
||||
import { searchLocalStatus } from './search'
|
||||
import deleteItem from './timeline/deleteItem'
|
||||
import editItem from './timeline/editItem'
|
||||
import updateStatusProperty from './timeline/updateStatusProperty'
|
||||
|
||||
export type QueryKeyTimeline = [
|
||||
@ -284,8 +285,13 @@ export type MutationVarsTimelineUpdateAccountProperty = {
|
||||
}
|
||||
}
|
||||
|
||||
export type MutationVarsTimelineEditItem = {
|
||||
type: 'editItem'
|
||||
status: Mastodon.Status
|
||||
navigationState: (QueryKeyTimeline | undefined)[]
|
||||
}
|
||||
|
||||
export type MutationVarsTimelineDeleteItem = {
|
||||
// This is for deleting status and conversation
|
||||
type: 'deleteItem'
|
||||
source: 'statuses' | 'conversations'
|
||||
id: Mastodon.Status['id']
|
||||
@ -300,6 +306,7 @@ export type MutationVarsTimelineDomainBlock = {
|
||||
export type MutationVarsTimeline =
|
||||
| MutationVarsTimelineUpdateStatusProperty
|
||||
| MutationVarsTimelineUpdateAccountProperty
|
||||
| MutationVarsTimelineEditItem
|
||||
| MutationVarsTimelineDeleteItem
|
||||
| MutationVarsTimelineDomainBlock
|
||||
|
||||
@ -365,6 +372,8 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
case 'editItem':
|
||||
return { body: params.status }
|
||||
case 'deleteItem':
|
||||
return apiInstance<Mastodon.Conversation>({
|
||||
method: 'delete',
|
||||
@ -418,6 +427,10 @@ const useTimelineMutation = ({
|
||||
case 'updateStatusProperty':
|
||||
updateStatusProperty(params, navigationState)
|
||||
break
|
||||
case 'editItem':
|
||||
console.log('YES!!!')
|
||||
editItem(params)
|
||||
break
|
||||
case 'deleteItem':
|
||||
deleteItem(params, navigationState)
|
||||
break
|
||||
|
@ -10,20 +10,17 @@ const deleteItem = (
|
||||
if (!key) continue
|
||||
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(key, old => {
|
||||
if (old) {
|
||||
let foundToot: boolean = false
|
||||
old.pages = old.pages.map(page => {
|
||||
if (foundToot) return page
|
||||
if (!old) return old
|
||||
|
||||
page.body = (page.body as Mastodon.Status[]).filter(
|
||||
(item: Mastodon.Status) => item.id !== id
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => ({
|
||||
...page,
|
||||
body: (page.body as Mastodon.Status[]).filter(
|
||||
status => status.id !== id && status.reblog?.id !== id
|
||||
)
|
||||
|
||||
return page
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
return old
|
||||
})
|
||||
}
|
||||
}
|
||||
|
46
src/utils/queryHooks/timeline/editItem.ts
Normal file
46
src/utils/queryHooks/timeline/editItem.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { InfiniteData } from '@tanstack/react-query'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { MutationVarsTimelineEditItem, TimelineData } from '../timeline'
|
||||
|
||||
const editItem = ({ status, navigationState }: MutationVarsTimelineEditItem) => {
|
||||
for (const key of navigationState) {
|
||||
if (!key) continue
|
||||
|
||||
queryClient.setQueryData<InfiniteData<TimelineData>>(key, old => {
|
||||
if (!old) return old
|
||||
|
||||
let updated: boolean = false
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => {
|
||||
if (updated) return page
|
||||
|
||||
if (typeof (page.body as Mastodon.Notification[])[0].type === 'string') {
|
||||
;(page.body as Mastodon.Notification[]).forEach(no => {
|
||||
if (no.status?.reblog?.id === status.id) {
|
||||
updated = true
|
||||
no.status.reblog = { ...status }
|
||||
} else if (no.status?.id === status.id) {
|
||||
updated = true
|
||||
no.status = { ...status }
|
||||
}
|
||||
})
|
||||
} else {
|
||||
;(page.body as Mastodon.Status[]).forEach(toot => {
|
||||
if (toot.reblog?.id === status.id) {
|
||||
updated = true
|
||||
toot.reblog = { ...status }
|
||||
} else if (toot.id === status.id) {
|
||||
updated = true
|
||||
toot = { ...status }
|
||||
}
|
||||
})
|
||||
}
|
||||
return page
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default editItem
|
@ -39,10 +39,13 @@ const updateStatusProperty = (
|
||||
for (const key of navigationState) {
|
||||
if (!key) continue
|
||||
|
||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(key, old => {
|
||||
if (old) {
|
||||
let updated: boolean = false
|
||||
old.pages = old.pages.map(page => {
|
||||
queryClient.setQueryData<InfiniteData<TimelineData>>(key, old => {
|
||||
if (!old) return old
|
||||
|
||||
let updated: boolean = false
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map(page => {
|
||||
if (updated) return page
|
||||
|
||||
if (typeof (page.body as Mastodon.Conversation[])[0].unread === 'boolean') {
|
||||
@ -73,7 +76,6 @@ const updateStatusProperty = (
|
||||
return page
|
||||
})
|
||||
}
|
||||
return old
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,4 +9,4 @@ export const infinitePageParams = {
|
||||
}
|
||||
|
||||
export const flattenPages = <T>(data: InfiniteData<PagedResponse<T[]>> | undefined): T[] | [] =>
|
||||
data?.pages.map(page => page.body).flat() || []
|
||||
data?.pages.flatMap(page => page.body) || []
|
||||
|
Reference in New Issue
Block a user