Merge branch 'main' into l10n_main

This commit is contained in:
xmflsct 2022-02-13 22:18:03 +01:00 committed by GitHub
commit be6913c830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 278 additions and 26 deletions

View File

@ -4,7 +4,7 @@
"native": "220206", "native": "220206",
"major": 3, "major": 3,
"minor": 4, "minor": 4,
"patch": 4, "patch": 5,
"expo": "44.0.0" "expo": "44.0.0"
}, },
"description": "tooot app for Mastodon", "description": "tooot app for Mastodon",

View File

@ -14,6 +14,7 @@ import pushUseConnect from '@utils/push/useConnect'
import pushUseReceive from '@utils/push/useReceive' import pushUseReceive from '@utils/push/useReceive'
import pushUseRespond from '@utils/push/useRespond' import pushUseRespond from '@utils/push/useRespond'
import { updatePreviousTab } from '@utils/slices/contextsSlice' import { updatePreviousTab } from '@utils/slices/contextsSlice'
import { checkEmojis } from '@utils/slices/instances/checkEmojis'
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
import { updateConfiguration } from '@utils/slices/instances/updateConfiguration' import { updateConfiguration } from '@utils/slices/instances/updateConfiguration'
import { updateFilters } from '@utils/slices/instances/updateFilters' import { updateFilters } from '@utils/slices/instances/updateFilters'
@ -92,6 +93,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
dispatch(updateConfiguration()) dispatch(updateConfiguration())
dispatch(updateFilters()) dispatch(updateFilters())
dispatch(updateAccountPreferences()) dispatch(updateAccountPreferences())
dispatch(checkEmojis())
} }
}, [instanceActive]) }, [instanceActive])

View File

@ -2,6 +2,7 @@ import EmojisButton from '@components/Emojis/Button'
import EmojisList from '@components/Emojis/List' import EmojisList from '@components/Emojis/List'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useEmojisQuery } from '@utils/queryHooks/emojis' import { useEmojisQuery } from '@utils/queryHooks/emojis'
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
import { chunk, forEach, groupBy, sortBy } from 'lodash' import { chunk, forEach, groupBy, sortBy } from 'lodash'
import React, { import React, {
Dispatch, Dispatch,
@ -11,11 +12,16 @@ import React, {
useEffect, useEffect,
useReducer useReducer
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useSelector } from 'react-redux'
import EmojisContext, { emojisReducer } from './Emojis/helpers/EmojisContext' import EmojisContext, { emojisReducer } from './Emojis/helpers/EmojisContext'
const prefetchEmojis = ( const prefetchEmojis = (
sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[], sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[],
reduceMotionEnabled: boolean reduceMotionEnabled: boolean
) => { ) => {
const prefetches: { uri: string }[] = [] const prefetches: { uri: string }[] = []
@ -101,14 +107,28 @@ const ComponentEmojis: React.FC<Props> = ({
[value, selectionRange.current?.start, selectionRange.current?.end] [value, selectionRange.current?.start, selectionRange.current?.end]
) )
const { t } = useTranslation()
const { data } = useEmojisQuery({ options: { enabled } }) const { data } = useEmojisQuery({ options: { enabled } })
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => { useEffect(() => {
if (data && data.length) { if (data && data.length) {
let sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[] = [] let sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[] = []
forEach( forEach(
groupBy(sortBy(data, ['category', 'shortcode']), 'category'), groupBy(sortBy(data, ['category', 'shortcode']), 'category'),
(value, key) => sortedEmojis.push({ title: key, data: chunk(value, 5) }) (value, key) => sortedEmojis.push({ title: key, data: chunk(value, 5) })
) )
if (frequentEmojis.length) {
sortedEmojis.unshift({
title: t('componentEmojis:frequentUsed'),
data: chunk(
frequentEmojis.map(e => e.emoji),
5
)
})
}
emojisDispatch({ emojisDispatch({
type: 'load', type: 'load',
payload: sortedEmojis payload: sortedEmojis

View File

@ -1,4 +1,5 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { countInstanceEmoji } 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 { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -14,11 +15,13 @@ import {
View View
} from 'react-native' } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useDispatch } from 'react-redux'
import validUrl from 'valid-url' import validUrl from 'valid-url'
import EmojisContext from './helpers/EmojisContext' import EmojisContext from './helpers/EmojisContext'
const EmojisList = React.memo( const EmojisList = React.memo(
() => { () => {
const dispatch = useDispatch()
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { t } = useTranslation() const { t } = useTranslation()
@ -42,12 +45,13 @@ const EmojisList = React.memo(
return ( return (
<Pressable <Pressable
key={emoji.shortcode} key={emoji.shortcode}
onPress={() => onPress={() => {
emojisDispatch({ emojisDispatch({
type: 'shortcode', type: 'shortcode',
payload: `:${emoji.shortcode}:` payload: `:${emoji.shortcode}:`
}) })
} dispatch(countInstanceEmoji(emoji))
}}
> >
<FastImage <FastImage
accessibilityLabel={t( accessibilityLabel={t(

View File

@ -3,7 +3,10 @@ import { createContext, Dispatch } from 'react'
export type EmojisState = { export type EmojisState = {
enabled: boolean enabled: boolean
active: boolean active: boolean
emojis: { title: string; data: Mastodon.Emoji[][] }[] emojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[]
shortcode: Mastodon.Emoji['shortcode'] | null shortcode: Mastodon.Emoji['shortcode'] | null
} }

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1,3 @@
{
"frequentUsed": "Frequent used"
}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1 @@
{}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1 @@
{}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -30,7 +30,11 @@ import FastImage from 'react-native-fast-image'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { ComposeState } from './utils/types' import { ComposeState } from './utils/types'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice' import {
getInstanceConfigurationStatusCharsURL,
getInstanceFrequentEmojis
} from '@utils/slices/instancesSlice'
import { useTranslation } from 'react-i18next'
const prefetchEmojis = ( const prefetchEmojis = (
sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>, sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>,
@ -99,15 +103,29 @@ const ComposeRoot = React.memo(
} }
}, [composeState.tag]) }, [composeState.tag])
const { t } = useTranslation()
const { data: emojisData } = useEmojisQuery({}) const { data: emojisData } = useEmojisQuery({})
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => { useEffect(() => {
if (emojisData && emojisData.length) { if (emojisData && emojisData.length) {
let sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[] = [] const sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[] = []
forEach( forEach(
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'), groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
(value, key) => (value, key) =>
sortedEmojis.push({ title: key, data: chunk(value, 5) }) sortedEmojis.push({ title: key, data: chunk(value, 5) })
) )
if (frequentEmojis.length) {
sortedEmojis.unshift({
title: t('componentEmojis:frequentUsed'),
data: chunk(
frequentEmojis.map(e => e.emoji),
5
)
})
}
composeDispatch({ composeDispatch({
type: 'emoji', type: 'emoji',
payload: { ...composeState.emoji, emojis: sortedEmojis } payload: { ...composeState.emoji, emojis: sortedEmojis }

View File

@ -1,5 +1,6 @@
import haptics from '@components/haptics' import haptics from '@components/haptics'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { countInstanceEmoji } 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, { RefObject, useCallback, useContext, useEffect } from 'react' import React, { RefObject, useCallback, useContext, useEffect } from 'react'
@ -14,6 +15,7 @@ import {
View View
} from 'react-native' } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useDispatch } from 'react-redux'
import validUrl from 'valid-url' import validUrl from 'valid-url'
import updateText from '../../updateText' import updateText from '../../updateText'
import ComposeContext from '../../utils/createContext' import ComposeContext from '../../utils/createContext'
@ -27,6 +29,7 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { colors } = useTheme() const { colors } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useDispatch()
useEffect(() => { useEffect(() => {
const tagEmojis = findNodeHandle(accessibleRefEmojis.current) const tagEmojis = findNodeHandle(accessibleRefEmojis.current)
@ -53,13 +56,14 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
<Pressable <Pressable
key={emoji.shortcode} key={emoji.shortcode}
onPress={() => { onPress={() => {
haptics('Light')
updateText({ updateText({
composeState, composeState,
composeDispatch, composeDispatch,
newText: `:${emoji.shortcode}:`, newText: `:${emoji.shortcode}:`,
type: 'emoji' type: 'emoji'
}) })
haptics('Light') dispatch(countInstanceEmoji(emoji))
}} }}
> >
<FastImage <FastImage

View File

@ -40,7 +40,12 @@ export type ComposeState = {
} }
emoji: { emoji: {
active: boolean active: boolean
emojis: { title: string; data: Mastodon.Emoji[][] }[] | undefined emojis:
| {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[]
| undefined
} }
poll: { poll: {
active: boolean active: boolean

View File

@ -45,6 +45,13 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
scrolled.current = true scrolled.current = true
const pointer = flattenData.findIndex(({ id }) => id === toot.id) const pointer = flattenData.findIndex(({ id }) => id === toot.id)
if (pointer === -1) return if (pointer === -1) return
Sentry.Native.setContext('Scroll to Index', {
type: 'original',
index: pointer,
itemsLength: flattenData.length,
id: toot.id,
flattenData: flattenData.map(({ id }) => id)
})
try { try {
setTimeout(() => { setTimeout(() => {
flRef.current?.scrollToIndex({ flRef.current?.scrollToIndex({
@ -54,13 +61,6 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
}, 500) }, 500)
} catch (err) { } catch (err) {
if (Math.random() < 0.1) { if (Math.random() < 0.1) {
Sentry.Native.setContext('Scroll to Index', {
type: 'original',
index: pointer,
itemsLength: flattenData.length,
id: toot.id,
flattenData: flattenData.map(({ id }) => id)
})
Sentry.Native.captureException(err) Sentry.Native.captureException(err)
} }
} }
@ -74,6 +74,12 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
error => { error => {
const offset = error.averageItemLength * error.index const offset = error.averageItemLength * error.index
flRef.current?.scrollToOffset({ offset }) flRef.current?.scrollToOffset({ offset })
Sentry.Native.setContext('Scroll to Index', {
type: 'onScrollToIndexFailed',
index: error.index,
itemsLength,
id: toot.id
})
try { try {
error.index < itemsLength && error.index < itemsLength &&
setTimeout( setTimeout(
@ -86,12 +92,6 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
) )
} catch (err) { } catch (err) {
if (Math.random() < 0.1) { if (Math.random() < 0.1) {
Sentry.Native.setContext('Scroll to Index', {
type: 'onScrollToIndexFailed',
index: error.index,
itemsLength,
id: toot.id
})
Sentry.Native.captureException(err) Sentry.Native.captureException(err)
} }
} }

View File

@ -27,7 +27,7 @@ const instancesPersistConfig = {
key: 'instances', key: 'instances',
prefix, prefix,
storage: secureStorage, storage: secureStorage,
version: 7, version: 8,
// @ts-ignore // @ts-ignore
migrate: createMigrate(instancesMigration) migrate: createMigrate(instancesMigration)
} }

View File

@ -3,6 +3,7 @@ import { InstanceV4 } from './v4'
import { InstanceV5 } from './v5' import { InstanceV5 } from './v5'
import { InstanceV6 } from './v6' import { InstanceV6 } from './v6'
import { InstanceV7 } from './v7' import { InstanceV7 } from './v7'
import { InstanceV8 } from './v8'
const instancesMigration = { const instancesMigration = {
4: (state: InstanceV3): InstanceV4 => { 4: (state: InstanceV3): InstanceV4 => {
@ -76,6 +77,16 @@ const instancesMigration = {
} }
}) })
} }
},
8: (state: InstanceV7): InstanceV8 => {
return {
instances: state.instances.map(instance => {
return {
...instance,
frequentEmojis: []
}
})
}
} }
} }

View File

@ -0,0 +1,83 @@
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
type Instance = {
active: boolean
appData: {
clientId: string
clientSecret: string
}
url: string
token: string
uri: Mastodon.Instance['uri']
urls: Mastodon.Instance['urls']
account: {
id: Mastodon.Account['id']
acct: Mastodon.Account['acct']
avatarStatic: Mastodon.Account['avatar_static']
preferences: Mastodon.Preferences
}
max_toot_chars?: number // To be deprecated in v4
configuration?: Mastodon.Instance['configuration']
filters: Mastodon.Filter[]
notifications_filter: {
follow: boolean
favourite: boolean
reblog: boolean
mention: boolean
poll: boolean
follow_request: boolean
}
push: {
global: { loading: boolean; value: boolean }
decode: { loading: boolean; value: boolean }
alerts: {
follow: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['follow']
}
favourite: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['favourite']
}
reblog: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['reblog']
}
mention: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['mention']
}
poll: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['poll']
}
}
keys: {
auth?: string
public?: string // legacy
private?: string // legacy
}
}
timelinesLookback?: {
[key: string]: {
queryKey: QueryKeyTimeline
ids: Mastodon.Status['id'][]
}
}
mePage: {
lists: { shown: boolean }
announcements: { shown: boolean; unread: number }
}
drafts: ComposeStateDraft[]
frequentEmojis: {
emoji: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>
score: number
count: number
lastUsed: Date
}[]
}
export type InstanceV8 = {
instances: Instance[]
}

View File

@ -0,0 +1,15 @@
import apiInstance from '@api/instance'
import queryClient from '@helpers/queryClient'
import { createAsyncThunk } from '@reduxjs/toolkit'
export const checkEmojis = createAsyncThunk(
'instances/checkEmojis',
async (): Promise<Mastodon.Emoji[]> => {
const res = await apiInstance<Mastodon.Emoji[]>({
method: 'get',
url: 'custom_emojis'
}).then(res => res.body)
queryClient.setQueryData(['Emojis'], res)
return res
}
)

View File

@ -4,6 +4,7 @@ import { RootState } from '@root/store'
import { ComposeStateDraft } from '@screens/Compose/utils/types' import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import addInstance from './instances/add' import addInstance from './instances/add'
import { checkEmojis } from './instances/checkEmojis'
import removeInstance from './instances/remove' import removeInstance from './instances/remove'
import { updateAccountPreferences } from './instances/updateAccountPreferences' import { updateAccountPreferences } from './instances/updateAccountPreferences'
import { updateConfiguration } from './instances/updateConfiguration' import { updateConfiguration } from './instances/updateConfiguration'
@ -81,6 +82,12 @@ export type Instance = {
announcements: { shown: boolean; unread: number } announcements: { shown: boolean; unread: number }
} }
drafts: ComposeStateDraft[] drafts: ComposeStateDraft[]
frequentEmojis: {
emoji: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>
score: number
count: number
lastUsed: number
}[]
} }
export type InstancesState = { export type InstancesState = {
@ -184,6 +191,56 @@ const instancesSlice = createSlice({
...instances[activeIndex].mePage, ...instances[activeIndex].mePage,
...action.payload ...action.payload
} }
},
countInstanceEmoji: (
{ instances },
action: PayloadAction<Instance['frequentEmojis'][0]['emoji']>
) => {
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
const calculateScore = (emoji: Instance['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
var sign = score > 0 ? 1 : score === 0 ? 0 : -1
return (sign * order + seconds / HALF_LIFE) * 10
}
const activeIndex = findInstanceActive(instances)
const foundEmojiIndex = instances[activeIndex].frequentEmojis?.findIndex(
e =>
e.emoji.shortcode === action.payload.shortcode &&
e.emoji.url === action.payload.url
)
let newEmojisSort: Instance['frequentEmojis']
if (foundEmojiIndex > -1) {
newEmojisSort = instances[activeIndex].frequentEmojis
.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)
} else {
newEmojisSort = instances[activeIndex].frequentEmojis || []
const temp = {
emoji: action.payload,
score: 0,
count: 0,
lastUsed: new Date().getTime()
}
newEmojisSort.push({
...temp,
score: calculateScore(temp),
count: temp.count + 1
})
}
instances[activeIndex].frequentEmojis = newEmojisSort
.sort((a, b) => b.score - a.score)
.slice(0, 20)
} }
}, },
extraReducers: builder => { extraReducers: builder => {
@ -321,6 +378,22 @@ const instancesSlice = createSlice({
action.meta.arg.changed action.meta.arg.changed
].loading = true ].loading = true
}) })
// Check if frequently used emojis still exist
.addCase(checkEmojis.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].frequentEmojis = state.instances[
activeIndex
].frequentEmojis?.filter(emoji => {
return action.payload.find(
e =>
e.shortcode === emoji.emoji.shortcode && e.url === emoji.emoji.url
)
})
})
.addCase(checkEmojis.rejected, (_, action) => {
console.error(action.error)
})
} }
}) })
@ -394,6 +467,10 @@ export const getInstanceMePage = ({ instances: { instances } }: RootState) =>
export const getInstanceDrafts = ({ instances: { instances } }: RootState) => export const getInstanceDrafts = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.drafts instances[findInstanceActive(instances)]?.drafts
export const getInstanceFrequentEmojis = ({
instances: { instances }
}: RootState) => instances[findInstanceActive(instances)]?.frequentEmojis
export const { export const {
updateInstanceActive, updateInstanceActive,
updateInstanceAccount, updateInstanceAccount,
@ -403,7 +480,8 @@ export const {
clearPushLoading, clearPushLoading,
disableAllPushes, disableAllPushes,
updateInstanceTimelineLookback, updateInstanceTimelineLookback,
updateInstanceMePage updateInstanceMePage,
countInstanceEmoji
} = instancesSlice.actions } = instancesSlice.actions
export default instancesSlice.reducer export default instancesSlice.reducer