mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Fixed #566
This commit is contained in:
		| @@ -1,10 +1 @@ | |||||||
| Enjoy toooting! This version includes following improvements and fixes: | Enjoy toooting! This version includes following improvements and fixes: | ||||||
| - Added Ukrainian (Slava Ukraini) |  | ||||||
| - Automatic setting detected language when tooting |  | ||||||
| - Remember public timeline type selection |  | ||||||
| - Show diffing of edit history |  | ||||||
| - Allow hiding boosts and replies in home timeline |  | ||||||
| - Support toot in RTL languages |  | ||||||
| - Added notification for admins |  | ||||||
| - Fix whole word filter matching |  | ||||||
| - Fix tablet cannot delete toot drafts |  | ||||||
|   | |||||||
| @@ -1,10 +1 @@ | |||||||
| toooting愉快!此版本包括以下改进和修复: | toooting愉快!此版本包括以下改进和修复: | ||||||
| - 增加乌克兰语(Slava Ukraini) |  | ||||||
| - 自动识别发嘟语言 |  | ||||||
| - 记住上次公共时间轴选项 |  | ||||||
| - 显示编辑历史的差异 |  | ||||||
| - 关注列表可隐藏转嘟和回复 |  | ||||||
| - 新增管理员推送通知 |  | ||||||
| - 支持嘟文右到左文字 |  | ||||||
| - 修复过滤整词功能 |  | ||||||
| - 修复平板不能删除草稿 |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "tooot", |   "name": "tooot", | ||||||
|   "version": "4.7.0", |   "version": "4.7.1", | ||||||
|   "description": "tooot for Mastodon", |   "description": "tooot for Mastodon", | ||||||
|   "author": "xmflsct <me@xmflsct.com>", |   "author": "xmflsct <me@xmflsct.com>", | ||||||
|   "license": "GPL-3.0-or-later", |   "license": "GPL-3.0-or-later", | ||||||
|   | |||||||
| @@ -64,6 +64,7 @@ const TimelineDefault: React.FC<Props> = ({ | |||||||
|     content: '', |     content: '', | ||||||
|     complete: false |     complete: false | ||||||
|   }) |   }) | ||||||
|  |   const detectedLanguage = useRef<string>(status.language || '') | ||||||
|  |  | ||||||
|   const filtered = queryKey && shouldFilter({ copiableContent, status, queryKey }) |   const filtered = queryKey && shouldFilter({ copiableContent, status, queryKey }) | ||||||
|   if (queryKey && filtered && !highlighted) { |   if (queryKey && filtered && !highlighted) { | ||||||
| @@ -139,6 +140,7 @@ const TimelineDefault: React.FC<Props> = ({ | |||||||
|         ownAccount, |         ownAccount, | ||||||
|         spoilerHidden, |         spoilerHidden, | ||||||
|         copiableContent, |         copiableContent, | ||||||
|  |         detectedLanguage, | ||||||
|         highlighted, |         highlighted, | ||||||
|         inThread: queryKey?.[1].page === 'Toot', |         inThread: queryKey?.[1].page === 'Toot', | ||||||
|         disableDetails, |         disableDetails, | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ type ContextType = { | |||||||
|     content: string |     content: string | ||||||
|     complete: boolean |     complete: boolean | ||||||
|   }> |   }> | ||||||
|  |   detectedLanguage?: React.MutableRefObject<string> | ||||||
|  |  | ||||||
|   highlighted?: boolean |   highlighted?: boolean | ||||||
|   inThread?: boolean |   inThread?: boolean | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import { StyleSheet, View } from 'react-native' | |||||||
| import StatusContext from './Context' | import StatusContext from './Context' | ||||||
|  |  | ||||||
| const TimelineFeedback = () => { | const TimelineFeedback = () => { | ||||||
|   const { status, highlighted } = useContext(StatusContext) |   const { status, highlighted, detectedLanguage } = useContext(StatusContext) | ||||||
|   if (!status || !highlighted) return null |   if (!status || !highlighted) return null | ||||||
|  |  | ||||||
|   const { t } = useTranslation('componentTimeline') |   const { t } = useTranslation('componentTimeline') | ||||||
| @@ -80,7 +80,12 @@ const TimelineFeedback = () => { | |||||||
|             accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')} |             accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')} | ||||||
|             accessibilityRole='button' |             accessibilityRole='button' | ||||||
|             style={[styles.text, { marginRight: 0, color: colors.blue }]} |             style={[styles.text, { marginRight: 0, color: colors.blue }]} | ||||||
|             onPress={() => navigation.push('Tab-Shared-History', { id: status.id })} |             onPress={() => | ||||||
|  |               navigation.push('Tab-Shared-History', { | ||||||
|  |                 id: status.id, | ||||||
|  |                 detectedLanguage: detectedLanguage?.current || status.language || '' | ||||||
|  |               }) | ||||||
|  |             } | ||||||
|           > |           > | ||||||
|             {t('shared.actionsUsers.history.text', { |             {t('shared.actionsUsers.history.text', { | ||||||
|               count: data.length - 1 |               count: data.length - 1 | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import { Circle } from 'react-native-animated-spinkit' | |||||||
| import StatusContext from './Context' | import StatusContext from './Context' | ||||||
|  |  | ||||||
| const TimelineTranslate = () => { | const TimelineTranslate = () => { | ||||||
|   const { status, highlighted, copiableContent } = useContext(StatusContext) |   const { status, highlighted, copiableContent, detectedLanguage } = useContext(StatusContext) | ||||||
|   if (!status || !highlighted) return null |   if (!status || !highlighted) return null | ||||||
|  |  | ||||||
|   const { t } = useTranslation('componentTimeline') |   const { t } = useTranslation('componentTimeline') | ||||||
| @@ -38,14 +38,19 @@ const TimelineTranslate = () => { | |||||||
|     ? [copiableContent?.current.content] |     ? [copiableContent?.current.content] | ||||||
|     : backupTextProcessing() |     : backupTextProcessing() | ||||||
|  |  | ||||||
|   const [detectedLanguage, setDetectedLanguage] = useState<{ |   const [detected, setDetected] = useState<{ | ||||||
|     language: string |     language: string | ||||||
|     confidence: number |     confidence: number | ||||||
|   }>({ language: status.language || '', confidence: 0 }) |   }>({ language: status.language || '', confidence: 0 }) | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const detect = async () => { |     const detect = async () => { | ||||||
|       const result = await detectLanguage(text.join('\n\n')) |       const result = await detectLanguage(text.join('\n\n')) | ||||||
|       result && setDetectedLanguage(result) |       if (result) { | ||||||
|  |         setDetected(result) | ||||||
|  |         if (detectedLanguage) { | ||||||
|  |           detectedLanguage.current = result.language | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     detect() |     detect() | ||||||
|   }, []) |   }, []) | ||||||
| @@ -57,7 +62,7 @@ const TimelineTranslate = () => { | |||||||
|  |  | ||||||
|   const [enabled, setEnabled] = useState(false) |   const [enabled, setEnabled] = useState(false) | ||||||
|   const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({ |   const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({ | ||||||
|     source: detectedLanguage.language, |     source: detected.language, | ||||||
|     target: targetLanguage, |     target: targetLanguage, | ||||||
|     text, |     text, | ||||||
|     options: { enabled } |     options: { enabled } | ||||||
| @@ -66,9 +71,9 @@ const TimelineTranslate = () => { | |||||||
|   const devView = () => { |   const devView = () => { | ||||||
|     return __DEV__ ? ( |     return __DEV__ ? ( | ||||||
|       <CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${ |       <CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${ | ||||||
|         detectedLanguage?.language |         detected?.language | ||||||
|       }; Confidence: ${ |       }; Confidence: ${ | ||||||
|         detectedLanguage?.confidence.toString().slice(0, 5) || 'null' |         detected?.confidence.toString().slice(0, 5) || 'null' | ||||||
|       }; Target: ${targetLanguage}`}</CustomText> |       }; Target: ${targetLanguage}`}</CustomText> | ||||||
|     ) : null |     ) : null | ||||||
|   } |   } | ||||||
| @@ -78,13 +83,13 @@ const TimelineTranslate = () => { | |||||||
|   } |   } | ||||||
|   if ( |   if ( | ||||||
|     Platform.OS === 'ios' && |     Platform.OS === 'ios' && | ||||||
|     Localization.locale.slice(0, 2).includes(detectedLanguage.language.slice(0, 2)) |     Localization.locale.slice(0, 2).includes(detected.language.slice(0, 2)) | ||||||
|   ) { |   ) { | ||||||
|     return devView() |     return devView() | ||||||
|   } |   } | ||||||
|   if ( |   if ( | ||||||
|     Platform.OS === 'android' && |     Platform.OS === 'android' && | ||||||
|     settingsLanguage?.slice(0, 2).includes(detectedLanguage.language.slice(0, 2)) |     settingsLanguage?.slice(0, 2).includes(detected.language.slice(0, 2)) | ||||||
|   ) { |   ) { | ||||||
|     return devView() |     return devView() | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -11,25 +11,45 @@ import { TabSharedStackScreenProps } from '@utils/navigation/navigators' | |||||||
| import { useStatusHistory } from '@utils/queryHooks/statusesHistory' | import { useStatusHistory } from '@utils/queryHooks/statusesHistory' | ||||||
| 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 { diffWords } from 'diff' | import { diffChars, diffWords } from 'diff' | ||||||
| import React, { useEffect } from 'react' | import React, { useEffect } from 'react' | ||||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||||
| import { FlatList, View } from 'react-native' | import { FlatList, View } from 'react-native' | ||||||
|  |  | ||||||
|  | const SCRIPTS_WITHOUT_BOUNDARIES = [ | ||||||
|  |   'my', | ||||||
|  |   'zh', | ||||||
|  |   'ja', | ||||||
|  |   'kar', | ||||||
|  |   'km', | ||||||
|  |   'lp', | ||||||
|  |   'phag', | ||||||
|  |   'pwo', | ||||||
|  |   'kar', | ||||||
|  |   'lana', | ||||||
|  |   'th', | ||||||
|  |   'bo' | ||||||
|  | ] | ||||||
|  |  | ||||||
| const ContentView: React.FC<{ | const ContentView: React.FC<{ | ||||||
|  |   withoutBoundary: boolean | ||||||
|   item: Mastodon.StatusHistory |   item: Mastodon.StatusHistory | ||||||
|   prevItem?: Mastodon.StatusHistory |   prevItem?: Mastodon.StatusHistory | ||||||
| }> = ({ item, prevItem }) => { | }> = ({ withoutBoundary, item, prevItem }) => { | ||||||
|   const { colors } = useTheme() |   const { colors } = useTheme() | ||||||
|  |  | ||||||
|   const changesSpoiler = diffWords( |   const changesSpoiler = withoutBoundary | ||||||
|  |     ? diffChars( | ||||||
|         removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''), |         removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''), | ||||||
|         removeHTML(item.spoiler_text || '') |         removeHTML(item.spoiler_text || '') | ||||||
|       ) |       ) | ||||||
|   const changesContent = diffWords( |     : diffWords( | ||||||
|     removeHTML(prevItem?.content || item.content), |         removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''), | ||||||
|     removeHTML(item.content) |         removeHTML(item.spoiler_text || '') | ||||||
|       ) |       ) | ||||||
|  |   const changesContent = withoutBoundary | ||||||
|  |     ? diffChars(removeHTML(prevItem?.content || item.content), removeHTML(item.content)) | ||||||
|  |     : diffWords(removeHTML(prevItem?.content || item.content), removeHTML(item.content)) | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     // @ts-ignore |     // @ts-ignore | ||||||
| @@ -91,7 +111,7 @@ const ContentView: React.FC<{ | |||||||
| const TabSharedHistory: React.FC<TabSharedStackScreenProps<'Tab-Shared-History'>> = ({ | const TabSharedHistory: React.FC<TabSharedStackScreenProps<'Tab-Shared-History'>> = ({ | ||||||
|   navigation, |   navigation, | ||||||
|   route: { |   route: { | ||||||
|     params: { id } |     params: { id, detectedLanguage } | ||||||
|   } |   } | ||||||
| }) => { | }) => { | ||||||
|   const { t } = useTranslation('screenTabs') |   const { t } = useTranslation('screenTabs') | ||||||
| @@ -106,12 +126,20 @@ const TabSharedHistory: React.FC<TabSharedStackScreenProps<'Tab-Shared-History'> | |||||||
|  |  | ||||||
|   const dataReversed = data ? [...data].reverse() : [] |   const dataReversed = data ? [...data].reverse() : [] | ||||||
|  |  | ||||||
|  |   const withoutBoundary = !!SCRIPTS_WITHOUT_BOUNDARIES.filter(script => | ||||||
|  |     detectedLanguage?.toLocaleLowerCase().startsWith(script) | ||||||
|  |   ).length | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <FlatList |     <FlatList | ||||||
|       style={{ flex: 1, minHeight: '100%' }} |       style={{ flex: 1, minHeight: '100%' }} | ||||||
|       data={dataReversed} |       data={dataReversed} | ||||||
|       renderItem={({ item, index }) => ( |       renderItem={({ item, index }) => ( | ||||||
|         <ContentView item={item} prevItem={dataReversed[index + 1]} /> |         <ContentView | ||||||
|  |           withoutBoundary={withoutBoundary} | ||||||
|  |           item={item} | ||||||
|  |           prevItem={dataReversed[index + 1]} | ||||||
|  |         /> | ||||||
|       )} |       )} | ||||||
|       ItemSeparatorComponent={ComponentSeparator} |       ItemSeparatorComponent={ComponentSeparator} | ||||||
|     /> |     /> | ||||||
|   | |||||||
| @@ -103,6 +103,7 @@ export type TabSharedStackParamList = { | |||||||
|   } |   } | ||||||
|   'Tab-Shared-History': { |   'Tab-Shared-History': { | ||||||
|     id: Mastodon.Status['id'] |     id: Mastodon.Status['id'] | ||||||
|  |     detectedLanguage: string | ||||||
|   } |   } | ||||||
|   'Tab-Shared-Search': undefined |   'Tab-Shared-Search': undefined | ||||||
|   'Tab-Shared-Toot': { |   'Tab-Shared-Toot': { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user