1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Merge branch 'main' into candidate

This commit is contained in:
xmflsct
2023-02-03 14:47:59 +01:00
31 changed files with 164 additions and 130 deletions

View File

@ -297,7 +297,7 @@ const ParseHTML: React.FC<Props> = ({
<Text
children={document.children.map(renderNode)}
onTextLayout={({ nativeEvent }) => {
if (numberOfLines === 1 || nativeEvent.lines.length >= numberOfLines + 5) {
if (numberOfLines === 1 || nativeEvent.lines.length >= numberOfLines + 8) {
setTotalLines(nativeEvent.lines.length)
}
}}

View File

@ -3,6 +3,7 @@ import { ParseEmojis } from '@components/Parse'
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { TabLocalStackParamList } from '@utils/navigation/navigators'
import { getAccountStorage } from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react'
@ -37,8 +38,7 @@ const TimelineActioned: React.FC<Props> = ({ action, isNotification, ...rest })
/>
)
const onPress = () =>
navigation.push('Tab-Shared-Account', { account })
const onPress = () => navigation.push('Tab-Shared-Account', { account })
const children = () => {
switch (action) {
@ -109,6 +109,7 @@ const TimelineActioned: React.FC<Props> = ({ action, isNotification, ...rest })
</>
)
case 'reblog':
const myself = rest.rootStatus?.account.id === getAccountStorage.string('auth.account.id')
return (
<>
<Icon
@ -121,6 +122,8 @@ const TimelineActioned: React.FC<Props> = ({ action, isNotification, ...rest })
{content(
isNotification
? t('shared.actioned.reblog.notification', { name })
: myself
? t('shared.actioned.reblog.myself')
: t('shared.actioned.reblog.default', { name })
)}
</Pressable>

View File

@ -1,7 +1,7 @@
import ComponentSeparator from '@components/Separator'
import CustomText from '@components/Text'
import TimelineDefault from '@components/Timeline/Default'
import { useScrollToTop } from '@react-navigation/native'
import { useNavigation, useScrollToTop } from '@react-navigation/native'
import { UseInfiniteQueryOptions } from '@tanstack/react-query'
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
import { flattenPages } from '@utils/queryHooks/utils'
@ -12,7 +12,8 @@ import {
} from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useRef, useState } from 'react'
import { throttle } from 'lodash'
import React, { RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
import Animated, {
@ -55,6 +56,7 @@ const Timeline: React.FC<Props> = ({
readMarker = undefined,
customProps
}) => {
const navigation = useNavigation()
const { colors, theme } = useTheme()
const { t } = useTranslation('componentTimeline')
@ -134,6 +136,21 @@ const Timeline: React.FC<Props> = ({
[isFetching]
)
const latestMarker = useRef<string>()
const updateMarkers = useCallback(
throttle(
() => readMarker && setAccountStorage([{ key: readMarker, value: latestMarker.current }]),
1000 * 15
),
[]
)
readMarker &&
useEffect(() => {
const unsubscribe = navigation.addListener('blur', () =>
setAccountStorage([{ key: readMarker, value: latestMarker.current }])
)
return unsubscribe
}, [])
const viewabilityConfigCallbackPairs = useRef<
Pick<FlatListProps<any>, 'viewabilityConfigCallbackPairs'>['viewabilityConfigCallbackPairs']
>(
@ -155,9 +172,11 @@ const Timeline: React.FC<Props> = ({
firstItemId &&
firstItemId > (marker || '0')
) {
setAccountStorage([{ key: readMarker, value: firstItemId }])
latestMarker.current = firstItemId
updateMarkers()
} else {
// setAccountStorage([{ key: readMarker, value: '105250709762254246' }])
// latestMarker.current = '105250709762254246'
// updateMarkers()
}
}
}
@ -218,15 +237,11 @@ const Timeline: React.FC<Props> = ({
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
}
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
ItemSeparatorComponent={({ leadingItem }) =>
queryKey[1].page === 'Toot' && queryKey[1].toot === leadingItem.id ? (
<ComponentSeparator extraMarginLeft={0} />
) : (
<ComponentSeparator
extraMarginLeft={StyleConstants.Avatar.M + StyleConstants.Spacing.S}
/>
)
}
ItemSeparatorComponent={() => (
<ComponentSeparator
extraMarginLeft={StyleConstants.Avatar.M + StyleConstants.Spacing.S}
/>
)}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
{...(!isLoading &&
!isFetching && {

View File

@ -33,6 +33,7 @@
"poll": "S'ha acabat una enquesta en què havies participat",
"reblog": {
"default": "{{name}} ha impulsat",
"myself": "",
"notification": "{{name}} ha impulsat la teva publicació"
},
"update": "L'impuls ha sigut editat",

View File

@ -33,6 +33,7 @@
"poll": "",
"reblog": {
"default": "",
"myself": "",
"notification": ""
},
"update": "",

View File

@ -6,22 +6,22 @@
"action_false": "Folgen",
"action_true": "Nutzer entfolgen"
},
"inLists": "",
"inLists": "Listen mit diesem Nutzer…",
"showBoosts": {
"action_false": "",
"action_true": ""
"action_false": "Zeige Boosts des Kontos",
"action_true": "Boosts des Users ausblenden"
},
"mute": {
"action_false": "Profil stummschalten",
"action_true": "Stummschaltung des Nutzers aufheben"
},
"followAs": {
"trigger": "",
"trigger": "Folgen als…",
"succeed_default": "Folgt nun @{{target}} als @{{source}}",
"succeed_locked": "Follower-Anfrage an @{{target}} mit {{source}} gesendet, Bestätigung ausstehend",
"failed": "Folgen als…"
},
"blockReport": "",
"blockReport": "Blockieren und melden",
"block": {
"action_false": "Nutzer blockieren",
"action_true": "User entblocken",
@ -56,11 +56,11 @@
},
"hashtag": {
"follow": {
"action_false": "",
"action_true": ""
"action_false": "Folgen",
"action_true": "Entfolgen"
},
"filter": {
"action": ""
"action": "Hashtag filtern…"
}
},
"share": {
@ -99,8 +99,8 @@
"action_true": "Tröt nicht mehr anheften"
},
"filter": {
"action_false": "",
"action_true": ""
"action_false": "Tröts filtern…",
"action_true": "Filter bearbeiten…"
}
}
}

View File

@ -3,7 +3,7 @@
"textInput": {
"placeholder": "Domain der Instanz"
},
"whitelisted": "",
"whitelisted": "Von dieser Instanz können Tröts möglicherweise nicht ohne Login abgerufen werden.",
"button": "Login",
"information": {
"name": "Name",

View File

@ -17,10 +17,10 @@
"refresh": {
"fetchPreviousPage": "Neuere Einträge",
"refetch": "Zum letzten",
"fetching": "",
"fetching": "Neue Tröts werden abgerufen…",
"fetched": {
"none": "",
"found": ""
"none": "Kein neuerer Tröt",
"found": "{{count}} Tröts abgerufen"
}
},
"shared": {
@ -33,6 +33,7 @@
"poll": "Eine Umfrage, an der du teilgenommen hast, ist beendet",
"reblog": {
"default": "{{name}} hat geboostet",
"myself": "",
"notification": "{{name}} hat deinen Tröt geboostet"
},
"update": "Boost wurde bearbeitet",
@ -128,7 +129,7 @@
"muted": {
"accessibilityLabel": "Tröt stummgeschaltet"
},
"replies": "",
"replies": "Antworten <0 />",
"visibility": {
"direct": {
"accessibilityLabel": "Tröt ist eine Direktnachricht"

View File

@ -171,8 +171,8 @@
},
"preferencesFilters": {
"expired": "Abgelaufen",
"keywords_one": "",
"keywords_other": "",
"keywords_one": "{{count}} Stichwort",
"keywords_other": "{{count}} Stichwort",
"statuses_one": "{{count}} Tröt",
"statuses_other": "{{count}} Tröts",
"context": "Läuft in <0 /> ab",
@ -196,7 +196,7 @@
"604800": "Nach einer Woche",
"18144000": "Nach einem Monat"
},
"context": "",
"context": "Gilt für",
"contexts": {
"home": "Gefolgte und Listen",
"notifications": "Benachrichtigung",
@ -209,9 +209,9 @@
"warn": "Verdeckt, kann aber aufgedeckt werden",
"hide": "Vollständig verdeckt"
},
"keywords": "",
"keywords": "Treffer für diese Schlüsselwörter",
"keyword": "Stichwörter",
"statuses": ""
"statuses": "Passende Tröts"
},
"profile": {
"feedback": {
@ -400,29 +400,29 @@
"name": "<0 /><1>'s Medien</1>"
},
"filter": {
"name": "",
"existed": ""
"name": "Zum Filter hinzufügen",
"existed": "In diesen Filtern vorhanden"
},
"history": {
"name": "Bearbeitungsverlauf"
},
"report": {
"name": "",
"report": "",
"name": "{{acct}} melden",
"report": "Melden",
"forward": {
"heading": ""
"heading": "Anonym an die Instanz {{instance}} weiterleiten"
},
"reasons": {
"heading": "",
"spam": "",
"other": "",
"violation": ""
"heading": "Was stimmt mit diesem Konto nicht?",
"spam": "Das ist Spam",
"other": "Etwas anderes",
"violation": "Verstoß gegen die Instanzregeln"
},
"comment": {
"heading": ""
"heading": "Möchtest du sonst noch etwas hinzufügen?"
},
"violatedRules": {
"heading": ""
"heading": "Verstoß gegen die Instanzregeln"
}
},
"search": {

View File

@ -33,6 +33,7 @@
"poll": "Μια δημοσκόπηση στην οποία ψηφίσατε ολοκληρώθηκε",
"reblog": {
"default": "{{name}} προώθησε τη δημοσίευση",
"myself": "",
"notification": "{{name}} προώθησε τη δημοσίευσή σας"
},
"update": "Η προώθηση έχει υποστεί επεξεργασία",

View File

@ -33,6 +33,7 @@
"poll": "A poll you have voted in has ended",
"reblog": {
"default": "{{name}} boosted",
"myself": "I boosted",
"notification": "{{name}} boosted your toot"
},
"update": "Reblog has been edited",

View File

@ -33,6 +33,7 @@
"poll": "Una encuesta en la que has votado ha terminado",
"reblog": {
"default": "{{name}} ha impulsado",
"myself": "",
"notification": "{{name}} ha impulsado tu toot"
},
"update": "El impulso ha sido editado",

View File

@ -17,10 +17,10 @@
"refresh": {
"fetchPreviousPage": "Berrienak hemendik hasita",
"refetch": "Azkenekora arte",
"fetching": "",
"fetching": "Tut berriak eskuratzen...",
"fetched": {
"none": "",
"found": ""
"none": "Tut berririk ez",
"found": "{{count}} tut eskuratuak"
}
},
"shared": {
@ -33,6 +33,7 @@
"poll": "Erantzun zenuen inkesta bat amaitu da",
"reblog": {
"default": "{{name}}-(e)k bultzatu du",
"myself": "",
"notification": "{{name}}-(e)k zure tuta bultzatu du"
},
"update": "Bultzada editatua izan da",

View File

@ -33,6 +33,7 @@
"poll": "Un sondage auquel vous avez participé est maintenant terminé",
"reblog": {
"default": "{{name}} a partagé",
"myself": "",
"notification": "{{name}} a partagé votre message"
},
"update": "Le reblog a été modifié",

View File

@ -33,6 +33,7 @@
"poll": "Un sondaggio in cui hai votato è terminato",
"reblog": {
"default": "{{name}} ha ricondiviso",
"myself": "",
"notification": "{{name}} ha ricondiviso il tuo toot"
},
"update": "Il link è stato modificato",

View File

@ -6,7 +6,7 @@
"action_false": "ユーザーをフォロー",
"action_true": "ユーザーをフォロー解除"
},
"inLists": "",
"inLists": "ユーザーを含むリスト一覧",
"showBoosts": {
"action_false": "ブーストを表示する",
"action_true": "ブーストを非表示にする"
@ -16,12 +16,12 @@
"action_true": "ユーザーのミュートを解除"
},
"followAs": {
"trigger": "",
"trigger": "... としてフォロー",
"succeed_default": "{{source}} として @{{target}} をフォローしました",
"succeed_locked": "{{source}} として @{{target}} へのフォロー申請を送りました。現在承認待ちです",
"failed": "別ユーザーとしてフォロー"
},
"blockReport": "",
"blockReport": "ブロックと通報",
"block": {
"action_false": "ユーザーをブロック",
"action_true": "ユーザーのブロックを解除",
@ -56,11 +56,11 @@
},
"hashtag": {
"follow": {
"action_false": "",
"action_true": ""
"action_false": "フォロー",
"action_true": "フォロー解除"
},
"filter": {
"action": ""
"action": "ハッシュタグをフィルター"
}
},
"share": {
@ -99,8 +99,8 @@
"action_true": "トゥートの固定を解除"
},
"filter": {
"action_false": "",
"action_true": ""
"action_false": "トゥートのフィルター",
"action_true": "フィルターの管理"
}
}
}

View File

@ -17,7 +17,7 @@
"refresh": {
"fetchPreviousPage": "前のページを取得",
"refetch": "更新",
"fetching": "",
"fetching": "新しいトゥートを取得しています...",
"fetched": {
"none": "",
"found": ""
@ -33,6 +33,7 @@
"poll": "アンケートが終了しました",
"reblog": {
"default": "{{name}}さんがブースト",
"myself": "",
"notification": "{{name}}さんがあなたのトゥートをブーストしました"
},
"update": "ブーストしたトゥートが編集されました",

View File

@ -70,13 +70,13 @@
"name": "プッシュ通知"
},
"preferences": {
"name": ""
"name": "設定"
},
"preferencesFilters": {
"name": ""
},
"preferencesFilterAdd": {
"name": ""
"name": "フィルターを作成"
},
"preferencesFilterEdit": {
"name": ""
@ -138,9 +138,9 @@
"visibility": {
"title": "",
"options": {
"public": "",
"unlisted": "",
"private": ""
"public": "公開",
"unlisted": "未収載",
"private": "フォロワー限定"
}
},
"sensitive": {
@ -170,7 +170,7 @@
}
},
"preferencesFilters": {
"expired": "",
"expired": "期限切れ",
"keywords_one": "",
"keywords_other": "",
"statuses_one": "",
@ -178,29 +178,29 @@
"context": "",
"contexts": {
"home": "",
"notifications": "",
"public": "",
"thread": "",
"account": ""
"notifications": "通知",
"public": "連合",
"thread": "会話",
"account": "プロフィール"
}
},
"preferencesFilter": {
"name": "",
"expiration": "",
"name": "名前",
"expiration": "有効期限",
"expirationOptions": {
"0": "",
"1800": "",
"3600": "",
"43200": "",
"86400": "",
"604800": "",
"18144000": ""
"0": "無期限",
"1800": "30分後",
"3600": "1時間後",
"43200": "12時間後",
"86400": "1日後",
"604800": "1週間後",
"18144000": "1ヶ月後"
},
"context": "",
"contexts": {
"home": "",
"notifications": "",
"public": "",
"notifications": "通知",
"public": "連合タイムライン",
"thread": "",
"account": ""
},
@ -210,7 +210,7 @@
"hide": ""
},
"keywords": "",
"keyword": "",
"keyword": "キーワード",
"statuses": ""
},
"profile": {
@ -400,7 +400,7 @@
"name": "<0 /><1>のメディア</1>"
},
"filter": {
"name": "",
"name": "フィルターに追加",
"existed": ""
},
"history": {

View File

@ -33,6 +33,7 @@
"poll": "내가 참여한 투표가 끝났어요",
"reblog": {
"default": "{{name}} 님이 부스트했어요",
"myself": "",
"notification": "{{name}} 님이 내 툿을 부스트했어요"
},
"update": "부스트한 툿이 수정됨",

View File

@ -33,6 +33,7 @@
"poll": "Een poll waaraan jij hebt meegedaan is beëindigd",
"reblog": {
"default": "{{name}} boostte",
"myself": "Ik boostte",
"notification": "{{name}} boostte jouw toot"
},
"update": "De reblog is bewerkt",

View File

@ -33,6 +33,7 @@
"poll": "Ankieta w której brałeś(-aś) udział zakończyło się",
"reblog": {
"default": "{{name}} podbił(a)",
"myself": "",
"notification": "{{name}} podbił(a) Twojego tootka"
},
"update": "Podbity tootek został edytowany",

View File

@ -33,6 +33,7 @@
"poll": "Uma enquete em que você votou terminou",
"reblog": {
"default": "{{name}} boostou",
"myself": "",
"notification": "{{name}} deu boost no teu toot"
},
"update": "Toot foi editado",
@ -60,7 +61,7 @@
"accessibilityLabel": "Adicionar este toot aos favoritos",
"function": "Salvos"
},
"openReport": ""
"openReport": "Abrir denúncia"
},
"actionsUsers": {
"reblogged_by": {

View File

@ -73,7 +73,7 @@
"name": "Preferências"
},
"preferencesFilters": {
"name": ""
"name": "Todos os filtros de conteúdo"
},
"preferencesFilterAdd": {
"name": "Criar um Filtro"
@ -150,8 +150,8 @@
"title": "",
"options": {
"default": "Ocultar mídia marcada como sensível",
"show_all": "",
"hide_all": ""
"show_all": "Sempre mostrar mídias",
"hide_all": "Sempre ocultar mídias"
}
},
"spoilers": {
@ -300,10 +300,10 @@
"heading": "Toot foi editado"
},
"admin.sign_up": {
"heading": ""
"heading": "Admin: cadastros"
},
"admin.report": {
"heading": ""
"heading": "Admin: denúncias"
},
"howitworks": "Saiba como funciona o roteamento"
},

View File

@ -33,6 +33,7 @@
"poll": "",
"reblog": {
"default": "",
"myself": "",
"notification": ""
},
"update": "",

View File

@ -33,6 +33,7 @@
"poll": "En omröstning du röstat i har avslutats",
"reblog": {
"default": "{{name}} boostade",
"myself": "Jag boostade",
"notification": "{{name}} boostade ditt inlägg"
},
"update": "Boosten har redigerats",

View File

@ -33,6 +33,7 @@
"poll": "Опитування, у якому ви голосували, закінчилося",
"reblog": {
"default": "{{name}} передмухує",
"myself": "",
"notification": "{{name}} передмухує ваш дмух"
},
"update": "Передмух був відредагований",

View File

@ -33,6 +33,7 @@
"poll": "Cuộc bình chọn đã kết thúc",
"reblog": {
"default": "{{name}} đăng lại",
"myself": "",
"notification": "{{name}} đăng lại tút của bạn"
},
"update": "Đăng lại đã được sửa",

View File

@ -33,6 +33,7 @@
"poll": "你参与的投票已结束",
"reblog": {
"default": "{{name}} 转嘟了",
"myself": "我转嘟了",
"notification": "{{name}} 转嘟了你的嘟文"
},
"update": "转嘟已被编辑",

View File

@ -33,6 +33,7 @@
"poll": "您曾投過的投票已經結束",
"reblog": {
"default": "{{name}} 轉嘟了",
"myself": "我轉嘟了",
"notification": "{{name}} 轉嘟了您的嘟文"
},
"update": "轉嘟已編輯",

View File

@ -79,22 +79,20 @@ const TabMePush: React.FC = () => {
<MenuRow
key={alert}
title={t(`me.push.${alert}.heading`)}
// switchDisabled={!pushEnabled || !push.global}
switchValue={push?.alerts[alert]}
switchOnValueChange={async () => {
const alerts = { ...push?.alerts, [alert]: !push?.alerts[alert] }
const body: { data: { alerts: { [key: string]: boolean } } } = {
data: { alerts: {} }
}
for (const [key, value] of Object.entries(alerts)) {
body.data.alerts[key] = value
}
await apiInstance<Mastodon.PushSubscription>({
method: 'put',
url: 'push/subscription',
body
})
if (pushEnabled && push.global) {
const body: { data: { alerts: Mastodon.PushSubscription['alerts'] } } = {
data: { alerts }
}
await apiInstance<Mastodon.PushSubscription>({
method: 'put',
url: 'push/subscription',
body
})
}
setAccountStorage([{ key: 'push', value: { ...push, alerts } }])
}}
@ -109,22 +107,22 @@ const TabMePush: React.FC = () => {
<MenuRow
key={type}
title={t(`me.push.${type}.heading`)}
// switchDisabled={!pushEnabled || !push.global}
switchValue={push?.alerts[type]}
switchOnValueChange={async () => {
const alerts = { ...push?.alerts, [type]: !push?.alerts[type] }
const body: { data: { alerts: { [key: string]: boolean } } } = {
data: { alerts: {} }
}
for (const [key, value] of Object.entries(alerts)) {
body.data.alerts[key] = value
}
await apiInstance<Mastodon.PushSubscription>({
method: 'put',
url: 'push/subscription',
body
})
if (pushEnabled && push.global) {
const body: {
data: { alerts: Mastodon.PushSubscription['alerts'] }
} = {
data: { alerts }
}
await apiInstance<Mastodon.PushSubscription>({
method: 'put',
url: 'push/subscription',
body
})
}
setAccountStorage([{ key: 'push', value: { ...push, alerts } }])
}}
@ -198,7 +196,10 @@ const TabMePush: React.FC = () => {
const endpoint = `https://${TOOOT_API_DOMAIN}/push/send/${pushPath}/${randomPath}`
const body: { subscription: any; alerts: { [key: string]: boolean } } = {
const body: {
subscription: any
alerts: Mastodon.PushSubscription['alerts']
} = {
subscription: {
endpoint,
keys: {
@ -207,10 +208,7 @@ const TabMePush: React.FC = () => {
auth: authKey
}
},
alerts: {}
}
for (const [key, value] of Object.entries(push.alerts)) {
body.alerts[key] = value
alerts: push.alerts
}
const res = await apiInstance<Mastodon.PushSubscription>({
@ -226,6 +224,10 @@ const TabMePush: React.FC = () => {
message: t('me.push.missingServerKey.message'),
description: t('me.push.missingServerKey.description')
})
await apiInstance({
method: 'delete',
url: 'push/subscription'
})
Sentry.setContext('Push server key', {
instance: domain,
resBody: res.body
@ -242,6 +244,11 @@ const TabMePush: React.FC = () => {
serverKey: res.body.server_key,
auth: push.decode === false ? null : authKey
}
}).catch(async () => {
await apiInstance({
method: 'delete',
url: 'push/subscription'
})
})
setAccountStorage([

View File

@ -61,20 +61,9 @@ export const queryFunctionTimeline = async ({
let marker: string | undefined
if (page.page === 'Following' && !pageParam?.offset && !pageParam?.min_id && !pageParam?.max_id) {
const storedMarker = getAccountStorage.string('read_marker_following')
if (storedMarker) {
await apiInstance<Mastodon.Status[]>({
method: 'get',
url: 'timelines/home',
params: { limit: 1, min_id: storedMarker }
}).then(res => {
if (res.body.length) {
marker = storedMarker
}
})
}
marker = getAccountStorage.string('read_marker_following')
}
let params: { [key: string]: string } = marker
const params: { [key: string]: string } = marker
? { limit: 40, max_id: marker }
: { limit: 40, ...pageParam }