mirror of https://github.com/tooot-app/app
Merge branch 'main' into l10n_main
This commit is contained in:
commit
be6913c830
|
@ -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",
|
||||||
|
|
|
@ -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])
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"frequentUsed": "Frequent used"
|
||||||
|
}
|
|
@ -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'),
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -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'),
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -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'),
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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[]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
)
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue