mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Continue refine remote logic #638
This commit is contained in:
@ -19,7 +19,6 @@ import {
|
||||
View
|
||||
} from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import validUrl from 'valid-url'
|
||||
import EmojisContext from './Context'
|
||||
|
||||
const EmojisList = () => {
|
||||
@ -68,83 +67,77 @@ const EmojisList = () => {
|
||||
>
|
||||
{item.map(emoji => {
|
||||
const uri = reduceMotionEnabled ? emoji.static_url : emoji.url
|
||||
if (validUrl.isHttpsUri(uri)) {
|
||||
return (
|
||||
<Pressable
|
||||
key={emoji.shortcode}
|
||||
onPress={() => {
|
||||
addEmoji(`:${emoji.shortcode}:`)
|
||||
return (
|
||||
<Pressable
|
||||
key={emoji.shortcode}
|
||||
onPress={() => {
|
||||
addEmoji(`:${emoji.shortcode}:`)
|
||||
|
||||
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
|
||||
const calculateScore = (
|
||||
emoji: StorageAccount['emojis_frequent'][number]
|
||||
): number => {
|
||||
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
|
||||
var score = emoji.count + 1
|
||||
var order = Math.log(Math.max(score, 1)) / Math.LN10
|
||||
var sign = score > 0 ? 1 : score === 0 ? 0 : -1
|
||||
return (sign * order + seconds / HALF_LIFE) * 10
|
||||
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
|
||||
const calculateScore = (
|
||||
emoji: StorageAccount['emojis_frequent'][number]
|
||||
): number => {
|
||||
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
|
||||
var score = emoji.count + 1
|
||||
var order = Math.log(Math.max(score, 1)) / Math.LN10
|
||||
var sign = score > 0 ? 1 : score === 0 ? 0 : -1
|
||||
return (sign * order + seconds / HALF_LIFE) * 10
|
||||
}
|
||||
|
||||
const currentEmojis = getAccountStorage.object('emojis_frequent')
|
||||
const foundEmojiIndex = currentEmojis?.findIndex(
|
||||
e => e.emoji.shortcode === emoji.shortcode && e.emoji.url === emoji.url
|
||||
)
|
||||
|
||||
let newEmojisSort: StorageAccount['emojis_frequent']
|
||||
if (foundEmojiIndex === -1) {
|
||||
newEmojisSort = currentEmojis || []
|
||||
const temp = {
|
||||
emoji,
|
||||
score: 0,
|
||||
count: 0,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
newEmojisSort.push({
|
||||
...temp,
|
||||
score: calculateScore(temp),
|
||||
count: temp.count + 1
|
||||
})
|
||||
} else {
|
||||
newEmojisSort =
|
||||
currentEmojis
|
||||
?.map((e, i) =>
|
||||
i === foundEmojiIndex
|
||||
? {
|
||||
...e,
|
||||
score: calculateScore(e),
|
||||
count: e.count + 1,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
: e
|
||||
)
|
||||
.sort((a, b) => b.score - a.score) || []
|
||||
}
|
||||
|
||||
const currentEmojis = getAccountStorage.object('emojis_frequent')
|
||||
const foundEmojiIndex = currentEmojis?.findIndex(
|
||||
e => e.emoji.shortcode === emoji.shortcode && e.emoji.url === emoji.url
|
||||
)
|
||||
|
||||
let newEmojisSort: StorageAccount['emojis_frequent']
|
||||
if (foundEmojiIndex === -1) {
|
||||
newEmojisSort = currentEmojis || []
|
||||
const temp = {
|
||||
emoji,
|
||||
score: 0,
|
||||
count: 0,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
newEmojisSort.push({
|
||||
...temp,
|
||||
score: calculateScore(temp),
|
||||
count: temp.count + 1
|
||||
})
|
||||
} else {
|
||||
newEmojisSort =
|
||||
currentEmojis
|
||||
?.map((e, i) =>
|
||||
i === foundEmojiIndex
|
||||
? {
|
||||
...e,
|
||||
score: calculateScore(e),
|
||||
count: e.count + 1,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
: e
|
||||
)
|
||||
.sort((a, b) => b.score - a.score) || []
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'emojis_frequent',
|
||||
value: newEmojisSort.sort((a, b) => b.score - a.score).slice(0, 20)
|
||||
}
|
||||
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'emojis_frequent',
|
||||
value: newEmojisSort.sort((a, b) => b.score - a.score).slice(0, 20)
|
||||
}
|
||||
])
|
||||
}}
|
||||
style={{ padding: StyleConstants.Spacing.S }}
|
||||
>
|
||||
<FastImage
|
||||
accessibilityLabel={t('common:customEmoji.accessibilityLabel', {
|
||||
emoji: emoji.shortcode
|
||||
})}
|
||||
accessibilityHint={t(
|
||||
'screenCompose:content.root.footer.emojis.accessibilityHint'
|
||||
)}
|
||||
source={{ uri }}
|
||||
style={{ width: 32, height: 32 }}
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
])
|
||||
}}
|
||||
style={{ padding: StyleConstants.Spacing.S }}
|
||||
>
|
||||
<FastImage
|
||||
accessibilityLabel={t('common:customEmoji.accessibilityLabel', {
|
||||
emoji: emoji.shortcode
|
||||
})}
|
||||
accessibilityHint={t('screenCompose:content.root.footer.emojis.accessibilityHint')}
|
||||
source={{ uri }}
|
||||
style={{ width: 32, height: 32 }}
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ import apiGeneral from '@utils/api/general'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { redirectUri, useAppsMutation } from '@utils/queryHooks/apps'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { storage } from '@utils/storage'
|
||||
@ -19,7 +19,6 @@ import {
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import * as Random from 'expo-random'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { RefObject, useCallback, useState } from 'react'
|
||||
@ -27,7 +26,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { MMKV } from 'react-native-mmkv'
|
||||
import validUrl from 'valid-url'
|
||||
import parse from 'url-parse'
|
||||
import CustomText from '../Text'
|
||||
|
||||
export interface Props {
|
||||
@ -50,7 +49,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
const whitelisted: boolean =
|
||||
!!domain.length &&
|
||||
!!errorCode &&
|
||||
!!validUrl.isHttpsUri(`https://${domain}`) &&
|
||||
!!(parse(`https://${domain}/`).hostname === domain) &&
|
||||
errorCode === 401
|
||||
|
||||
const instanceQuery = useInstanceQuery({
|
||||
|
@ -7,7 +7,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Platform, TextStyle } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import validUrl from 'valid-url'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
|
||||
@ -72,23 +71,19 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
const uri = reduceMotionEnabled
|
||||
? emojis[emojiIndex].static_url
|
||||
: emojis[emojiIndex].url
|
||||
if (validUrl.isHttpsUri(uri)) {
|
||||
return (
|
||||
<CustomText key={emojiShortcode + i}>
|
||||
{i === 0 ? ' ' : undefined}
|
||||
<FastImage
|
||||
source={{ uri }}
|
||||
style={{
|
||||
width: adaptedFontsize,
|
||||
height: adaptedFontsize,
|
||||
transform: [{ translateY: Platform.OS === 'ios' ? -1 : 2 }]
|
||||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<CustomText key={emojiShortcode + i}>
|
||||
{i === 0 ? ' ' : undefined}
|
||||
<FastImage
|
||||
source={{ uri: uri.trim() }}
|
||||
style={{
|
||||
width: adaptedFontsize,
|
||||
height: adaptedFontsize,
|
||||
transform: [{ translateY: Platform.OS === 'ios' ? -1 : 2 }]
|
||||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return <CustomText key={i}>{str}</CustomText>
|
||||
|
@ -3,9 +3,10 @@ import GracefullyImage from '@components/GracefullyImage'
|
||||
import openLink from '@components/openLink'
|
||||
import CustomText from '@components/Text'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { matchAccount, matchStatus } from '@utils/helpers/urlMatcher'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { useStatusQuery } from '@utils/queryHooks/status'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -20,97 +21,44 @@ const TimelineCard: React.FC = () => {
|
||||
if (!status || !status.card) return null
|
||||
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const isStatus = matchStatus(status.card.url)
|
||||
const match = urlMatcher(status.card.url)
|
||||
const [foundStatus, setFoundStatus] = useState<Mastodon.Status>()
|
||||
const isAccount = matchAccount(status.card.url)
|
||||
const [foundAccount, setFoundAccount] = useState<Mastodon.Account>()
|
||||
|
||||
const searchQuery = useSearchQuery({
|
||||
type: (() => {
|
||||
if (isStatus) return 'statuses'
|
||||
if (isAccount) return 'accounts'
|
||||
})(),
|
||||
term: (() => {
|
||||
if (isStatus) {
|
||||
if (isStatus.sameInstance) {
|
||||
return
|
||||
} else {
|
||||
return status.card.url
|
||||
}
|
||||
}
|
||||
if (isAccount) {
|
||||
if (isAccount.sameInstance) {
|
||||
if (isAccount.style === 'default') {
|
||||
return
|
||||
} else {
|
||||
return isAccount.username
|
||||
}
|
||||
} else {
|
||||
return status.card.url
|
||||
}
|
||||
}
|
||||
})(),
|
||||
limit: 1,
|
||||
options: { enabled: false }
|
||||
})
|
||||
|
||||
const statusQuery = useStatusQuery({
|
||||
id: isStatus?.id || '',
|
||||
status: match?.status ? { ...match.status, uri: status.card.url } : undefined,
|
||||
options: { enabled: false }
|
||||
})
|
||||
useEffect(() => {
|
||||
if (isStatus) {
|
||||
if (match?.status) {
|
||||
setLoading(true)
|
||||
if (isStatus.sameInstance) {
|
||||
statusQuery
|
||||
.refetch()
|
||||
.then(res => {
|
||||
res.data && setFoundStatus(res.data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
} else {
|
||||
searchQuery
|
||||
.refetch()
|
||||
.then(res => {
|
||||
const status = (res.data as any)?.statuses?.[0]
|
||||
status && setFoundStatus(status)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
}
|
||||
statusQuery
|
||||
.refetch()
|
||||
.then(res => {
|
||||
res.data && setFoundStatus(res.data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const accountQuery = useAccountQuery({
|
||||
account:
|
||||
isAccount?.style === 'default' ? { id: isAccount.id, url: status.card.url } : undefined,
|
||||
account: match?.account ? { ...match?.account, url: status.card.url } : undefined,
|
||||
options: { enabled: false }
|
||||
})
|
||||
useEffect(() => {
|
||||
if (isAccount) {
|
||||
if (match?.account) {
|
||||
setLoading(true)
|
||||
if (isAccount.sameInstance && isAccount.style === 'default') {
|
||||
accountQuery
|
||||
.refetch()
|
||||
.then(res => {
|
||||
res.data && setFoundAccount(res.data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
} else {
|
||||
searchQuery
|
||||
.refetch()
|
||||
.then(res => {
|
||||
const account = (res.data as any)?.accounts?.[0]
|
||||
account && setFoundAccount(account)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
}
|
||||
accountQuery
|
||||
.refetch()
|
||||
.then(res => {
|
||||
res.data && setFoundAccount(res.data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -129,10 +77,10 @@ const TimelineCard: React.FC = () => {
|
||||
</View>
|
||||
)
|
||||
}
|
||||
if (isStatus && foundStatus) {
|
||||
if (match?.status && foundStatus) {
|
||||
return <TimelineDefault item={foundStatus} disableDetails disableOnPress />
|
||||
}
|
||||
if (isAccount && foundAccount) {
|
||||
if (match?.account && foundAccount) {
|
||||
return <ComponentAccount account={foundAccount} />
|
||||
}
|
||||
return (
|
||||
@ -198,7 +146,18 @@ const TimelineCard: React.FC = () => {
|
||||
overflow: 'hidden',
|
||||
borderColor: colors.border
|
||||
}}
|
||||
onPress={async () => status.card && (await openLink(status.card.url, navigation))}
|
||||
onPress={async () => {
|
||||
if (match?.status && foundStatus) {
|
||||
navigation.push('Tab-Shared-Toot', { toot: foundStatus })
|
||||
return
|
||||
}
|
||||
if (match?.account && foundAccount) {
|
||||
navigation.push('Tab-Shared-Account', { account: foundAccount })
|
||||
return
|
||||
}
|
||||
|
||||
status.card?.url && (await openLink(status.card.url, navigation))
|
||||
}}
|
||||
children={cardContent()}
|
||||
/>
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ const TimelineFeedback = () => {
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const { data } = useStatusHistory({
|
||||
id: status.id,
|
||||
status,
|
||||
options: { enabled: status.edited_at !== undefined }
|
||||
})
|
||||
|
||||
@ -82,7 +82,7 @@ const TimelineFeedback = () => {
|
||||
style={[styles.text, { marginRight: 0, color: colors.blue }]}
|
||||
onPress={() =>
|
||||
navigation.push('Tab-Shared-History', {
|
||||
id: status.id,
|
||||
status,
|
||||
detectedLanguage: detectedLanguage?.current || status.language || ''
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import CustomText from '@components/Text'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { QueryKeyFilters } from '@utils/queryHooks/filters'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
@ -73,13 +73,8 @@ const HeaderConversation = ({ conversation }: Props) => {
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
{conversation.last_status?.created_at ? (
|
||||
<HeaderSharedCreated
|
||||
created_at={conversation.last_status?.created_at}
|
||||
edited_at={conversation.last_status?.edited_at}
|
||||
/>
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={conversation.last_status?.muted} />
|
||||
{conversation.last_status?.created_at ? <HeaderSharedCreated /> : null}
|
||||
<HeaderSharedMuted />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
@ -17,7 +17,7 @@ import HeaderSharedReplies from './HeaderShared/Replies'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
const TimelineHeaderDefault: React.FC = () => {
|
||||
const { queryKey, rootQueryKey, status, highlighted, disableDetails, rawContent, isRemote } =
|
||||
const { queryKey, rootQueryKey, status, disableDetails, rawContent, isRemote } =
|
||||
useContext(StatusContext)
|
||||
if (!status) return null
|
||||
|
||||
@ -66,15 +66,11 @@ const TimelineHeaderDefault: React.FC = () => {
|
||||
style={{ marginRight: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
) : null}
|
||||
<HeaderSharedCreated
|
||||
created_at={status.created_at}
|
||||
edited_at={status.edited_at}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
<HeaderSharedVisibility visibility={status.visibility} />
|
||||
<HeaderSharedMuted muted={status.muted} />
|
||||
<HeaderSharedCreated />
|
||||
<HeaderSharedVisibility />
|
||||
<HeaderSharedMuted />
|
||||
<HeaderSharedReplies />
|
||||
<HeaderSharedApplication application={status.application} />
|
||||
<HeaderSharedApplication />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
@ -146,15 +146,10 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={notification.status?.created_at || notification.created_at}
|
||||
edited_at={notification.status?.edited_at}
|
||||
/>
|
||||
{notification.status?.visibility ? (
|
||||
<HeaderSharedVisibility visibility={notification.status.visibility} />
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||
<HeaderSharedApplication application={notification.status?.application} />
|
||||
<HeaderSharedCreated />
|
||||
{notification.status?.visibility ? <HeaderSharedVisibility /> : null}
|
||||
<HeaderSharedMuted />
|
||||
<HeaderSharedApplication />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
@ -2,32 +2,31 @@ import openLink from '@components/openLink'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import StatusContext from '../Context'
|
||||
|
||||
export interface Props {
|
||||
application?: Mastodon.Application
|
||||
}
|
||||
|
||||
const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
|
||||
const HeaderSharedApplication: React.FC = () => {
|
||||
const { status } = useContext(StatusContext)
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
|
||||
return application && application.name !== 'Web' ? (
|
||||
return status?.application?.name && status.application.name !== 'Web' ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
accessibilityRole='link'
|
||||
onPress={async () => {
|
||||
application.website && (await openLink(application.website))
|
||||
status.application?.website && (await openLink(status.application.website))
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
marginLeft: StyleConstants.Spacing.S,
|
||||
color: colors.secondary
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{t('shared.header.shared.application', {
|
||||
application: application.name
|
||||
application: status.application.name
|
||||
})}
|
||||
</CustomText>
|
||||
) : null
|
||||
|
@ -3,21 +3,23 @@ import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FormattedDate } from 'react-intl'
|
||||
import StatusContext from '../Context'
|
||||
|
||||
export interface Props {
|
||||
created_at: Mastodon.Status['created_at'] | number
|
||||
edited_at?: Mastodon.Status['edited_at']
|
||||
highlighted?: boolean
|
||||
created_at?: Mastodon.Status['created_at'] | number
|
||||
}
|
||||
|
||||
const HeaderSharedCreated: React.FC<Props> = ({ created_at, edited_at, highlighted = false }) => {
|
||||
const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
|
||||
const { status, highlighted } = useContext(StatusContext)
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const actualTime = edited_at || created_at
|
||||
if (!status) return null
|
||||
|
||||
const actualTime = created_at || status.edited_at || status.created_at
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -30,7 +32,7 @@ const HeaderSharedCreated: React.FC<Props> = ({ created_at, edited_at, highlight
|
||||
<RelativeTime time={actualTime} />
|
||||
)}
|
||||
</CustomText>
|
||||
{edited_at ? (
|
||||
{status.edited_at && !highlighted ? (
|
||||
<Icon
|
||||
accessibilityLabel={t('shared.header.shared.edited.accessibilityLabel')}
|
||||
name='Edit'
|
||||
|
@ -1,18 +1,16 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import StatusContext from '../Context'
|
||||
|
||||
export interface Props {
|
||||
muted?: Mastodon.Status['muted']
|
||||
}
|
||||
|
||||
const HeaderSharedMuted: React.FC<Props> = ({ muted }) => {
|
||||
const HeaderSharedMuted: React.FC = () => {
|
||||
const { status } = useContext(StatusContext)
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { colors } = useTheme()
|
||||
|
||||
return muted ? (
|
||||
return status?.muted ? (
|
||||
<Icon
|
||||
accessibilityLabel={t('shared.header.shared.muted.accessibilityLabel')}
|
||||
name='VolumeX'
|
||||
|
@ -1,19 +1,17 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import StatusContext from '../Context'
|
||||
|
||||
export interface Props {
|
||||
visibility: Mastodon.Status['visibility']
|
||||
}
|
||||
|
||||
const HeaderSharedVisibility: React.FC<Props> = ({ visibility }) => {
|
||||
const HeaderSharedVisibility: React.FC = () => {
|
||||
const { status } = useContext(StatusContext)
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { colors } = useTheme()
|
||||
|
||||
switch (visibility) {
|
||||
switch (status?.visibility) {
|
||||
case 'unlisted':
|
||||
return (
|
||||
<Icon
|
||||
|
@ -47,7 +47,7 @@ const menuAccount = ({
|
||||
setEnabled(true)
|
||||
}
|
||||
}, [openChange, enabled])
|
||||
const { data: fetchedAccount } = useAccountQuery({ account, options: { enabled } })
|
||||
const { data: fetchedAccount } = useAccountQuery({ account, _local: true, options: { enabled } })
|
||||
const actualAccount = status?._remote ? fetchedAccount : account
|
||||
const { data, isFetched } = useRelationshipQuery({
|
||||
id: actualAccount?.id,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { getHost } from '@utils/helpers/urlMatcher'
|
||||
import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import parse from 'url-parse'
|
||||
|
||||
const menuInstance = ({
|
||||
status,
|
||||
@ -35,9 +35,9 @@ const menuInstance = ({
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const instance = getHost(status.uri)
|
||||
const instance = parse(status.uri).hostname
|
||||
|
||||
if (instance === getAccountStorage.string('auth.domain')) {
|
||||
if (instance !== getAccountStorage.string('auth.domain')) {
|
||||
menus.push([
|
||||
{
|
||||
key: 'instance-block',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { QueryKeyInstance } from '@utils/queryHooks/instance'
|
||||
import i18next from 'i18next'
|
||||
import { Asset, launchImageLibrary } from 'react-native-image-picker'
|
||||
|
@ -1,12 +1,13 @@
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { matchAccount, matchStatus } from '@utils/helpers/urlMatcher'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { SearchResult } from '@utils/queryHooks/search'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { QueryKeyAccount } from '@utils/queryHooks/account'
|
||||
import { searchLocalAccount, searchLocalStatus } from '@utils/queryHooks/search'
|
||||
import { QueryKeyStatus } from '@utils/queryHooks/status'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import validUrl from 'valid-url'
|
||||
|
||||
export let loadingLink = false
|
||||
|
||||
@ -15,7 +16,7 @@ const openLink = async (url: string, navigation?: any) => {
|
||||
return
|
||||
}
|
||||
|
||||
const handleNavigation = (page: 'Tab-Shared-Toot' | 'Tab-Shared-Account', options: {}) => {
|
||||
const handleNavigation = (page: 'Tab-Shared-Toot' | 'Tab-Shared-Account', options: any) => {
|
||||
if (navigation) {
|
||||
navigation.push(page, options)
|
||||
} else {
|
||||
@ -24,83 +25,79 @@ const openLink = async (url: string, navigation?: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const match = urlMatcher(url)
|
||||
// If a tooot can be found
|
||||
const isStatus = matchStatus(url)
|
||||
if (isStatus) {
|
||||
if (isStatus.sameInstance) {
|
||||
handleNavigation('Tab-Shared-Toot', { toot: { id: isStatus.id } })
|
||||
return
|
||||
}
|
||||
|
||||
if (match?.status?.id) {
|
||||
loadingLink = true
|
||||
let response
|
||||
try {
|
||||
response = await apiInstance<SearchResult>({
|
||||
version: 'v2',
|
||||
method: 'get',
|
||||
url: 'search',
|
||||
params: { type: 'statuses', q: url, limit: 1, resolve: true }
|
||||
})
|
||||
} catch {}
|
||||
if (response && response.body && response.body.statuses.length) {
|
||||
handleNavigation('Tab-Shared-Toot', {
|
||||
toot: response.body.statuses[0]
|
||||
})
|
||||
let response: Mastodon.Status | undefined = undefined
|
||||
|
||||
const queryKey: QueryKeyStatus = [
|
||||
'Status',
|
||||
{ id: match.status.id, uri: url, _remote: match.status._remote }
|
||||
]
|
||||
const cache = queryClient.getQueryData<Mastodon.Status>(queryKey)
|
||||
|
||||
if (cache) {
|
||||
handleNavigation('Tab-Shared-Toot', { toot: cache })
|
||||
loadingLink = false
|
||||
return
|
||||
} else {
|
||||
try {
|
||||
response = await searchLocalStatus(url)
|
||||
} catch {}
|
||||
if (response) {
|
||||
handleNavigation('Tab-Shared-Toot', { toot: response })
|
||||
loadingLink = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If an account can be found
|
||||
const isAccount = matchAccount(url)
|
||||
if (isAccount) {
|
||||
if (isAccount.sameInstance) {
|
||||
if (isAccount.style === 'default' && isAccount.id) {
|
||||
handleNavigation('Tab-Shared-Account', { account: isAccount })
|
||||
return
|
||||
}
|
||||
if (match?.account) {
|
||||
if (!match.account._remote && match.account.id) {
|
||||
handleNavigation('Tab-Shared-Account', { account: match.account.id })
|
||||
return
|
||||
}
|
||||
|
||||
loadingLink = true
|
||||
let response
|
||||
try {
|
||||
response = await apiInstance<SearchResult>({
|
||||
version: 'v2',
|
||||
method: 'get',
|
||||
url: 'search',
|
||||
params: {
|
||||
type: 'accounts',
|
||||
q: isAccount.sameInstance && isAccount.style === 'pretty' ? isAccount.username : url,
|
||||
limit: 1,
|
||||
resolve: true
|
||||
}
|
||||
})
|
||||
} catch {}
|
||||
if (response && response.body && response.body.accounts.length) {
|
||||
handleNavigation('Tab-Shared-Account', {
|
||||
account: response.body.accounts[0]
|
||||
})
|
||||
let response: Mastodon.Account | undefined = undefined
|
||||
|
||||
const queryKey: QueryKeyAccount = [
|
||||
'Account',
|
||||
{ id: match.account.id, url: url, _remote: match.account._remote }
|
||||
]
|
||||
const cache = queryClient.getQueryData<Mastodon.Status>(queryKey)
|
||||
|
||||
if (cache) {
|
||||
handleNavigation('Tab-Shared-Account', { account: cache })
|
||||
loadingLink = false
|
||||
return
|
||||
} else {
|
||||
try {
|
||||
response = await searchLocalAccount(url)
|
||||
} catch {}
|
||||
if (response) {
|
||||
handleNavigation('Tab-Shared-Account', { account: response })
|
||||
loadingLink = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadingLink = false
|
||||
const validatedUrl = validUrl.isWebUri(url)
|
||||
if (validatedUrl) {
|
||||
switch (getGlobalStorage.string('app.browser')) {
|
||||
// Some links might end with an empty space at the end that triggers an error
|
||||
case 'internal':
|
||||
await WebBrowser.openBrowserAsync(validatedUrl, {
|
||||
dismissButtonStyle: 'close',
|
||||
enableBarCollapsing: true,
|
||||
...(await browserPackage())
|
||||
})
|
||||
break
|
||||
case 'external':
|
||||
await Linking.openURL(validatedUrl)
|
||||
break
|
||||
}
|
||||
switch (getGlobalStorage.string('app.browser')) {
|
||||
// Some links might end with an empty space at the end that triggers an error
|
||||
case 'internal':
|
||||
await WebBrowser.openBrowserAsync(url.trim(), {
|
||||
dismissButtonStyle: 'close',
|
||||
enableBarCollapsing: true,
|
||||
...(await browserPackage())
|
||||
})
|
||||
break
|
||||
case 'external':
|
||||
await Linking.openURL(url.trim())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user