This commit is contained in:
xmflsct 2022-11-20 16:14:08 +01:00
parent fbfae52627
commit 18e7262f6f
16 changed files with 297 additions and 267 deletions

View File

@ -475,7 +475,8 @@ declare namespace Mastodon {
// Base
name: string
url: string
// history: types
history: { day: string; accounts: string; uses: string }[]
following: boolean // Since v4.0
}
type WebSocketStream =

View File

@ -20,6 +20,7 @@ import * as SplashScreen from 'expo-splash-screen'
import React, { useCallback, useEffect, useState } from 'react'
import { LogBox, Platform } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { enableFreeze } from 'react-native-screens'
import { QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'
@ -95,13 +96,15 @@ const App: React.FC = () => {
}
return (
<ActionSheetProvider>
<AccessibilityManager>
<ThemeManager>
<Screens localCorrupt={localCorrupt} />
</ThemeManager>
</AccessibilityManager>
</ActionSheetProvider>
<SafeAreaProvider>
<ActionSheetProvider>
<AccessibilityManager>
<ThemeManager>
<Screens localCorrupt={localCorrupt} />
</ThemeManager>
</AccessibilityManager>
</ActionSheetProvider>
</SafeAreaProvider>
)
} else {
return null

View File

@ -4,10 +4,8 @@ import { useTheme } from '@utils/styles/ThemeManager'
import { getColors, Theme } from '@utils/styles/themes'
import React, { RefObject } from 'react'
import { AccessibilityInfo } from 'react-native'
import FlashMessage, {
hideMessage,
showMessage
} from 'react-native-flash-message'
import FlashMessage, { hideMessage, showMessage } from 'react-native-flash-message'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import haptics from './haptics'
const displayMessage = ({
@ -112,6 +110,7 @@ const removeMessage = () => {
const Message = React.forwardRef<FlashMessage>((_, ref) => {
const { colors, theme } = useTheme()
const insets = useSafeAreaInsets()
return (
<FlashMessage
@ -125,7 +124,8 @@ const Message = React.forwardRef<FlashMessage>((_, ref) => {
shadowOffset: { width: 0, height: 0 },
shadowOpacity: theme === 'light' ? 0.16 : 0.24,
shadowRadius: 4,
paddingRight: StyleConstants.Spacing.M * 2
paddingRight: StyleConstants.Spacing.M * 2,
marginTop: insets.top
}}
titleStyle={{
color: colors.primaryDefault,

View File

@ -23,5 +23,10 @@
"feature": "notification_types_positive_filter",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
},
{
"feature": "follow_tags",
"version": 4.0,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v4.0.0"
}
]

View File

@ -310,6 +310,13 @@
"attachments": {
"name": "<0 /><1>\"s media</1>"
},
"hashtag": {
"follow": "Follow",
"unfollow": "Unfollow"
},
"history": {
"name": "Edit History"
},
"search": {
"header": {
"prefix": "Searching",
@ -346,9 +353,6 @@
"reblogged_by": "{{count}} boosted",
"favourited_by": "{{count}} favourited"
}
},
"history": {
"name": "Edit History"
}
}
}

View File

@ -9,7 +9,6 @@ import * as VideoThumbnails from 'expo-video-thumbnails'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, Image, ScrollView, View } from 'react-native'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
const Share = ({
@ -76,9 +75,7 @@ const Share = ({
renderItem={({ item }) => (
<Image source={{ uri: item }} style={{ width: 88, height: 88 }} />
)}
ItemSeparatorComponent={() => (
<View style={{ width: StyleConstants.Spacing.S }} />
)}
ItemSeparatorComponent={() => <View style={{ width: StyleConstants.Spacing.S }} />}
/>
</View>
)
@ -99,64 +96,60 @@ const ScreenAccountSelection = ({
const instances = useSelector(getInstances, () => true)
return (
<SafeAreaProvider>
<ScrollView
style={{ marginBottom: StyleConstants.Spacing.L * 2 }}
keyboardShouldPersistTaps='always'
<ScrollView
style={{ marginBottom: StyleConstants.Spacing.L * 2 }}
keyboardShouldPersistTaps='always'
>
<View
style={{
marginHorizontal: StyleConstants.Spacing.Global.PagePadding
}}
>
<View
{share ? <Share {...share} /> : null}
<CustomText
fontStyle='M'
fontWeight='Bold'
style={{
marginHorizontal: StyleConstants.Spacing.Global.PagePadding
textAlign: 'center',
marginTop: StyleConstants.Spacing.L,
marginBottom: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
{share ? <Share {...share} /> : null}
<CustomText
fontStyle='M'
fontWeight='Bold'
style={{
textAlign: 'center',
marginTop: StyleConstants.Spacing.L,
marginBottom: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
{t('content.select_account')}
</CustomText>
<View
style={{
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: StyleConstants.Spacing.M
}}
>
{instances.length
? instances
.slice()
.sort((a, b) =>
`${a.uri}${a.account.acct}`.localeCompare(
`${b.uri}${b.account.acct}`
)
{t('content.select_account')}
</CustomText>
<View
style={{
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: StyleConstants.Spacing.M
}}
>
{instances.length
? instances
.slice()
.sort((a, b) =>
`${a.uri}${a.account.acct}`.localeCompare(`${b.uri}${b.account.acct}`)
)
.map((instance, index) => {
return (
<AccountButton
key={index}
instance={instance}
additionalActions={() => {
navigationRef.navigate('Screen-Compose', {
type: 'share',
...share
})
}}
/>
)
.map((instance, index) => {
return (
<AccountButton
key={index}
instance={instance}
additionalActions={() => {
navigationRef.navigate('Screen-Compose', {
type: 'share',
...share
})
}}
/>
)
})
: null}
</View>
})
: null}
</View>
</ScrollView>
</SafeAreaProvider>
</View>
</ScrollView>
)
}

View File

@ -3,11 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect } from 'react'
import { Dimensions, StyleSheet, View } from 'react-native'
import {
PanGestureHandler,
State,
TapGestureHandler
} from 'react-native-gesture-handler'
import { PanGestureHandler, State, TapGestureHandler } from 'react-native-gesture-handler'
import Animated, {
Extrapolate,
interpolate,
@ -17,10 +13,7 @@ import Animated, {
useSharedValue,
withTiming
} from 'react-native-reanimated'
import {
SafeAreaProvider,
useSafeAreaInsets
} from 'react-native-safe-area-context'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import ActionsAltText from './Actions/AltText'
import ActionsNotificationsFilter from './Actions/NotificationsFilter'
@ -39,12 +32,7 @@ const ScreenActions = ({
}, [])
const styleTop = useAnimatedStyle(() => {
return {
bottom: interpolate(
panY.value,
[0, screenHeight],
[0, -screenHeight],
Extrapolate.CLAMP
)
bottom: interpolate(panY.value, [0, screenHeight], [0, -screenHeight], Extrapolate.CLAMP)
}
})
const dismiss = useCallback(() => {
@ -73,45 +61,35 @@ const ScreenActions = ({
}
return (
<SafeAreaProvider>
<Animated.View style={{ flex: 1 }}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
dismiss()
}
}}
<Animated.View style={{ flex: 1 }}>
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
dismiss()
}
}}
>
<Animated.View
style={[styles.overlay, { backgroundColor: colors.backgroundOverlayInvert }]}
>
<Animated.View
style={[
styles.overlay,
{ backgroundColor: colors.backgroundOverlayInvert }
]}
>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: colors.backgroundDefault,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View
style={[
styles.handle,
{ backgroundColor: colors.primaryOverlay }
]}
/>
{actions()}
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
</Animated.View>
</SafeAreaProvider>
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View
style={[
styles.container,
styleTop,
{
backgroundColor: colors.backgroundDefault,
paddingBottom: insets.bottom || StyleConstants.Spacing.L
}
]}
>
<View style={[styles.handle, { backgroundColor: colors.primaryOverlay }]} />
{actions()}
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
</Animated.View>
)
}

View File

@ -20,7 +20,7 @@ import { Directions, Gesture, LongPressGestureHandler } from 'react-native-gestu
import { LiveTextImageView } from 'react-native-live-text-image-view'
import { runOnJS, useSharedValue } from 'react-native-reanimated'
import { Zoom, createZoomListComponent } from 'react-native-reanimated-zoom'
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import saveImage from './ImageViewer/save'
const ZoomFlatList = createZoomListComponent(FlatList)
@ -153,7 +153,7 @@ const ScreenImagesViewer = ({
)
return (
<SafeAreaProvider style={{ backgroundColor: 'black' }}>
<View style={{ backgroundColor: 'black' }}>
<StatusBar hidden />
<View
style={{
@ -232,7 +232,7 @@ const ScreenImagesViewer = ({
})}
/>
</LongPressGestureHandler>
</SafeAreaProvider>
</View>
)
}

View File

@ -12,7 +12,7 @@ import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import ContextMenu from 'react-native-context-menu-view'
import TabSharedRoot from './Shared/Root'
import TabShared from './Shared'
const Stack = createNativeStackNavigator<TabLocalStackParamList>()
@ -96,7 +96,7 @@ const TabLocal = React.memo(
/>
)}
/>
{TabSharedRoot({ Stack })}
{TabShared({ Stack })}
</Stack.Navigator>
)
},

View File

@ -16,7 +16,7 @@ import TabMeSettings from './Me/Settings'
import TabMeSettingsFontsize from './Me/SettingsFontsize'
import TabMeSettingsLanguage from './Me/SettingsLanguage'
import TabMeSwitch from './Me/Switch'
import TabSharedRoot from './Shared/Root'
import TabShared from './Shared'
const Stack = createNativeStackNavigator<TabMeStackParamList>()
@ -187,7 +187,7 @@ const TabMe = React.memo(
})}
/>
{TabSharedRoot({ Stack })}
{TabShared({ Stack })}
</Stack.Navigator>
)
},

View File

@ -10,7 +10,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React, { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import TabSharedRoot from './Shared/Root'
import TabShared from './Shared'
const Stack = createNativeStackNavigator<TabNotificationsStackParamList>()
@ -65,7 +65,7 @@ const TabNotifications = React.memo(
children={children}
options={screenOptionsRoot}
/>
{TabSharedRoot({ Stack })}
{TabShared({ Stack })}
</Stack.Navigator>
)
},

View File

@ -12,7 +12,7 @@ import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions } from 'react-native'
import { TabView } from 'react-native-tab-view'
import TabSharedRoot from './Shared/Root'
import TabShared from './Shared'
const Stack = createNativeStackNavigator<TabPublicStackParamList>()
@ -107,7 +107,7 @@ const TabPublic = React.memo(
return (
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
<Stack.Screen name='Tab-Public-Root' options={screenOptionsRoot} children={children} />
{TabSharedRoot({ Stack })}
{TabShared({ Stack })}
</Stack.Navigator>
)
},

View File

@ -1,24 +1,78 @@
import haptics from '@components/haptics'
import { HeaderRight } from '@components/Header'
import { displayMessage } from '@components/Message'
import Timeline from '@components/Timeline'
import TimelineDefault from '@components/Timeline/Default'
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
import { useTagsMutation, useTagsQuery } from '@utils/queryHooks/tags'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React from 'react'
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
const TabSharedHashtag: React.FC<
TabSharedStackScreenProps<'Tab-Shared-Hashtag'>
> = ({
const TabSharedHashtag: React.FC<TabSharedStackScreenProps<'Tab-Shared-Hashtag'>> = ({
navigation,
route: {
params: { hashtag }
}
}) => {
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Hashtag', hashtag }]
const { theme } = useTheme()
const { t } = useTranslation('screenTabs')
const canFollowTags = useSelector(checkInstanceFeature('follow_tags'))
const { data, isFetching, refetch } = useTagsQuery({
tag: hashtag,
options: { enabled: canFollowTags }
})
const mutation = useTagsMutation({
onSuccess: () => {
haptics('Success')
refetch()
},
onError: (err: any, { to }) => {
displayMessage({
theme,
type: 'error',
message: t('common:message.error.message', {
function: to ? t('shared.hashtag.follow') : t('shared.hashtag.unfollow')
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
}
})
useEffect(() => {
if (!canFollowTags) return
navigation.setOptions({
headerRight: () => (
<HeaderRight
loading={isFetching || mutation.isLoading}
type='text'
content={data?.following ? t('shared.hashtag.unfollow') : t('shared.hashtag.follow')}
onPress={() =>
typeof data?.following === 'boolean' &&
mutation.mutate({ tag: hashtag, type: 'follow', to: !data.following })
}
/>
)
})
}, [canFollowTags, data?.following, isFetching])
return (
<Timeline
queryKey={queryKey}
customProps={{
renderItem: ({ item }) => (
<TimelineDefault item={item} queryKey={queryKey} />
)
renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />
}}
/>
)

View File

@ -20,11 +20,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { Platform, TextInput, View } from 'react-native'
import ContextMenu, { ContextMenuAction } from 'react-native-context-menu-view'
const TabSharedRoot = ({
Stack
}: {
Stack: ReturnType<typeof createNativeStackNavigator>
}) => {
const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNavigator> }) => {
const { colors, mode } = useTheme()
const { t } = useTranslation('screenTabs')
@ -50,9 +46,7 @@ const TabSharedRoot = ({
backgroundColor: `rgba(255, 255, 255, 0)`
},
title: '',
headerLeft: () => (
<HeaderLeft onPress={() => navigation.goBack()} background />
),
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} background />,
headerRight: () => {
const actions: ContextMenuAction[] = []
@ -77,13 +71,10 @@ const TabSharedRoot = ({
dropdownMenuMode
>
<HeaderRight
accessibilityLabel={t(
'shared.account.actions.accessibilityLabel',
{ user: account.acct }
)}
accessibilityHint={t(
'shared.account.actions.accessibilityHint'
)}
accessibilityLabel={t('shared.account.actions.accessibilityLabel', {
user: account.acct
})}
accessibilityHint={t('shared.account.actions.accessibilityHint')}
content='MoreHorizontal'
onPress={() => {}}
background
@ -132,9 +123,7 @@ const TabSharedRoot = ({
key='Tab-Shared-Hashtag'
name='Tab-Shared-Hashtag'
component={TabSharedHashtag}
options={({
route
}: TabSharedStackScreenProps<'Tab-Shared-Hashtag'>) => ({
options={({ route }: TabSharedStackScreenProps<'Tab-Shared-Hashtag'>) => ({
title: `#${decodeURIComponent(route.params.hashtag)}`
})}
/>
@ -150,24 +139,16 @@ const TabSharedRoot = ({
key='Tab-Shared-Search'
name='Tab-Shared-Search'
component={TabSharedSearch}
options={({
navigation
}: TabSharedStackScreenProps<'Tab-Shared-Search'>) => ({
options={({ navigation }: TabSharedStackScreenProps<'Tab-Shared-Search'>) => ({
...(Platform.OS === 'ios'
? {
headerLeft: () => (
<HeaderLeft onPress={() => navigation.goBack()} />
)
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
}
: { headerLeft: () => null }),
headerTitle: () => {
const onChangeText = debounce(
(text: string) => navigation.setParams({ text }),
1000,
{
trailing: true
}
)
const onChangeText = debounce((text: string) => navigation.setParams({ text }), 1000, {
trailing: true
})
return (
<View
style={{
@ -199,9 +180,7 @@ const TabSharedRoot = ({
autoCorrect={false}
clearButtonMode='never'
keyboardType='web-search'
onSubmitEditing={({ nativeEvent: { text } }) =>
navigation.setParams({ text })
}
onSubmitEditing={({ nativeEvent: { text } }) => navigation.setParams({ text })}
placeholder={t('shared.search.header.placeholder')}
placeholderTextColor={colors.secondary}
returnKeyType='go'
@ -216,9 +195,7 @@ const TabSharedRoot = ({
key='Tab-Shared-Toot'
name='Tab-Shared-Toot'
component={TabSharedToot}
options={{
title: t('shared.toot.name')
}}
options={{ title: t('shared.toot.name') }}
/>
<Stack.Screen
@ -233,9 +210,7 @@ const TabSharedRoot = ({
title: t(`shared.users.${reference}.${type}`, { count }),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter
content={t(`shared.users.${reference}.${type}`, { count })}
/>
<HeaderCenter content={t(`shared.users.${reference}.${type}`, { count })} />
)
})
})}
@ -244,4 +219,4 @@ const TabSharedRoot = ({
)
}
export default TabSharedRoot
export default TabShared

View File

@ -0,0 +1,59 @@
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import {
QueryFunctionContext,
useMutation,
UseMutationOptions,
useQuery,
UseQueryOptions
} from 'react-query'
type QueryKeyFollowedTags = ['FollowedTags']
const useFollowedTagsQuery = ({
options
}: {
options?: UseQueryOptions<Mastodon.Tag, AxiosError>
}) => {
const queryKey: QueryKeyFollowedTags = ['FollowedTags']
return useQuery(
queryKey,
async ({ pageParam }: QueryFunctionContext<QueryKeyFollowedTags>) => {
const params: { [key: string]: string } = { ...pageParam }
const res = await apiInstance<Mastodon.Tag>({ method: 'get', url: `followed_tags`, params })
return res.body
},
options
)
}
type QueryKeyTags = ['Tags', { tag: string }]
const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyTags>) => {
const { tag } = queryKey[1]
return apiInstance<Mastodon.Tag>({ method: 'get', url: `tags/${tag}` }).then(res => res.body)
}
const useTagsQuery = ({
options,
...queryKeyParams
}: QueryKeyTags[1] & {
options?: UseQueryOptions<Mastodon.Tag, AxiosError>
}) => {
const queryKey: QueryKeyTags = ['Tags', { ...queryKeyParams }]
return useQuery(queryKey, queryFunction, options)
}
type MutationVarsAnnouncement = { tag: string; type: 'follow'; to: boolean }
const mutationFunction = async ({ tag, type, to }: MutationVarsAnnouncement) => {
switch (type) {
case 'follow':
return apiInstance<{}>({
method: 'post',
url: `tags/${tag}/${to ? 'follow' : 'unfollow'}`
})
}
}
const useTagsMutation = (options: UseMutationOptions<{}, AxiosError, MutationVarsAnnouncement>) => {
return useMutation(mutationFunction, options)
}
export { useFollowedTagsQuery, useTagsQuery, useTagsMutation }

View File

@ -29,10 +29,7 @@ const instancesSlice = createSlice({
name: 'instances',
initialState: instancesInitialState,
reducers: {
updateInstanceActive: (
{ instances },
action: PayloadAction<InstanceLatest>
) => {
updateInstanceActive: ({ instances }, action: PayloadAction<InstanceLatest>) => {
instances = instances.map(instance => {
instance.active =
instance.url === action.payload.url &&
@ -43,9 +40,7 @@ const instancesSlice = createSlice({
},
updateInstanceAccount: (
{ instances },
action: PayloadAction<
Pick<InstanceLatest['account'], 'acct' & 'avatarStatic'>
>
action: PayloadAction<Pick<InstanceLatest['account'], 'acct' & 'avatarStatic'>>
) => {
const activeIndex = findInstanceActive(instances)
instances[activeIndex].account = {
@ -60,10 +55,7 @@ const instancesSlice = createSlice({
const activeIndex = findInstanceActive(instances)
instances[activeIndex].notifications_filter = action.payload
},
updateInstanceDraft: (
{ instances },
action: PayloadAction<ComposeStateDraft>
) => {
updateInstanceDraft: ({ instances }, action: PayloadAction<ComposeStateDraft>) => {
const activeIndex = findInstanceActive(instances)
const draftIndex = instances[activeIndex].drafts.findIndex(
({ timestamp }) => timestamp === action.payload.timestamp
@ -74,10 +66,7 @@ const instancesSlice = createSlice({
instances[activeIndex].drafts[draftIndex] = action.payload
}
},
removeInstanceDraft: (
{ instances },
action: PayloadAction<ComposeStateDraft['timestamp']>
) => {
removeInstanceDraft: ({ instances }, action: PayloadAction<ComposeStateDraft['timestamp']>) => {
const activeIndex = findInstanceActive(instances)
instances[activeIndex].drafts = instances[activeIndex].drafts?.filter(
draft => draft.timestamp !== action.payload
@ -126,9 +115,7 @@ const instancesSlice = createSlice({
action: PayloadAction<InstanceLatest['frequentEmojis'][0]['emoji']>
) => {
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
const calculateScore = (
emoji: InstanceLatest['frequentEmojis'][0]
): number => {
const calculateScore = (emoji: InstanceLatest['frequentEmojis'][0]): number => {
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
var score = emoji.count + 1
var order = Math.log(Math.max(score, 1)) / Math.LN10
@ -137,9 +124,7 @@ const instancesSlice = createSlice({
}
const activeIndex = findInstanceActive(instances)
const foundEmojiIndex = instances[activeIndex].frequentEmojis?.findIndex(
e =>
e.emoji.shortcode === action.payload.shortcode &&
e.emoji.url === action.payload.url
e => e.emoji.shortcode === action.payload.shortcode && e.emoji.url === action.payload.url
)
let newEmojisSort: InstanceLatest['frequentEmojis']
if (foundEmojiIndex > -1) {
@ -147,11 +132,11 @@ const instancesSlice = createSlice({
.map((e, i) =>
i === foundEmojiIndex
? {
...e,
score: calculateScore(e),
count: e.count + 1,
lastUsed: new Date().getTime()
}
...e,
score: calculateScore(e),
count: e.count + 1,
lastUsed: new Date().getTime()
}
: e
)
.sort((a, b) => b.score - a.score)
@ -218,8 +203,7 @@ const instancesSlice = createSlice({
return true
}
})
state.instances.length &&
(state.instances[state.instances.length - 1].active = true)
state.instances.length && (state.instances[state.instances.length - 1].active = true)
analytics('logout')
})
@ -250,8 +234,7 @@ const instancesSlice = createSlice({
.addCase(updateConfiguration.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].version = action.payload?.version || '0'
state.instances[activeIndex].configuration =
action.payload.configuration
state.instances[activeIndex].configuration = action.payload.configuration
})
.addCase(updateConfiguration.rejected, (_, action) => {
console.error(action.error)
@ -291,22 +274,16 @@ const instancesSlice = createSlice({
// Update Instance Push Individual Alert
.addCase(updateInstancePushAlert.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.alerts[
action.meta.arg.changed
].loading = false
state.instances[activeIndex].push.alerts[action.meta.arg.changed].loading = false
state.instances[activeIndex].push.alerts = action.payload
})
.addCase(updateInstancePushAlert.rejected, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.alerts[
action.meta.arg.changed
].loading = false
state.instances[activeIndex].push.alerts[action.meta.arg.changed].loading = false
})
.addCase(updateInstancePushAlert.pending, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.alerts[
action.meta.arg.changed
].loading = true
state.instances[activeIndex].push.alerts[action.meta.arg.changed].loading = true
})
// Check if frequently used emojis still exist
@ -317,8 +294,7 @@ const instancesSlice = createSlice({
activeIndex
].frequentEmojis?.filter(emoji => {
return action.payload?.find(
e =>
e.shortcode === emoji.emoji.shortcode && e.url === emoji.emoji.url
e => e.shortcode === emoji.emoji.shortcode && e.url === emoji.emoji.url
)
})
})
@ -331,8 +307,7 @@ const instancesSlice = createSlice({
export const getInstanceActive = ({ instances: { instances } }: RootState) =>
findInstanceActive(instances)
export const getInstances = ({ instances: { instances } }: RootState) =>
instances
export const getInstances = ({ instances: { instances } }: RootState) => instances
export const getInstance = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]
@ -350,42 +325,30 @@ export const getInstanceVersion = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.version
export const checkInstanceFeature =
(feature: string) =>
({ instances: { instances } }: RootState): Boolean => {
return (
features
.filter(f => f.feature === feature)
.filter(
f =>
parseFloat(instances[findInstanceActive(instances)]?.version) >=
f.version
)?.length > 0
)
}
({ instances: { instances } }: RootState): boolean => {
return (
features
.filter(f => f.feature === feature)
.filter(f => parseFloat(instances[findInstanceActive(instances)]?.version) >= f.version)
?.length > 0
)
}
/* Get Instance Configuration */
export const getInstanceConfigurationStatusMaxChars = ({
instances: { instances }
}: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.statuses
.max_characters || 500
export const getInstanceConfigurationStatusMaxChars = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.statuses.max_characters || 500
export const getInstanceConfigurationStatusMaxAttachments = ({
instances: { instances }
}: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.statuses
.max_media_attachments || 4
instances[findInstanceActive(instances)]?.configuration?.statuses.max_media_attachments || 4
export const getInstanceConfigurationStatusCharsURL = ({
instances: { instances }
}: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.statuses
.characters_reserved_per_url || 23
export const getInstanceConfigurationStatusCharsURL = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.statuses.characters_reserved_per_url ||
23
export const getInstanceConfigurationMediaAttachments = ({
instances: { instances }
}: RootState) =>
instances[findInstanceActive(instances)]?.configuration
?.media_attachments || {
export const getInstanceConfigurationMediaAttachments = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.media_attachments || {
supported_mime_types: [
'image/jpeg',
'image/png',
@ -418,9 +381,7 @@ export const getInstanceConfigurationMediaAttachments = ({
video_matrix_limit: 2304000
}
export const getInstanceConfigurationPoll = ({
instances: { instances }
}: RootState) =>
export const getInstanceConfigurationPoll = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.configuration?.polls || {
max_options: 4,
max_characters_per_option: 50,
@ -432,16 +393,14 @@ export const getInstanceConfigurationPoll = ({
export const getInstanceAccount = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.account
export const getInstanceNotificationsFilter = ({
instances: { instances }
}: RootState) => instances[findInstanceActive(instances)]?.notifications_filter
export const getInstanceNotificationsFilter = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.notifications_filter
export const getInstancePush = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.push
export const getInstanceTimelinesLookback = ({
instances: { instances }
}: RootState) => instances[findInstanceActive(instances)]?.timelinesLookback
export const getInstanceTimelinesLookback = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.timelinesLookback
export const getInstanceMePage = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.mePage
@ -449,9 +408,8 @@ export const getInstanceMePage = ({ instances: { instances } }: RootState) =>
export const getInstanceDrafts = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.drafts
export const getInstanceFrequentEmojis = ({
instances: { instances }
}: RootState) => instances[findInstanceActive(instances)]?.frequentEmojis
export const getInstanceFrequentEmojis = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.frequentEmojis
export const {
updateInstanceActive,