This commit is contained in:
xmflsct 2022-12-17 23:21:56 +01:00
parent 3691b19a87
commit c59690fcb9
9 changed files with 65 additions and 41 deletions

View File

@ -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

View File

@ -1,10 +1 @@
toooting愉快此版本包括以下改进和修复 toooting愉快此版本包括以下改进和修复
- 增加乌克兰语Slava Ukraini
- 自动识别发嘟语言
- 记住上次公共时间轴选项
- 显示编辑历史的差异
- 关注列表可隐藏转嘟和回复
- 新增管理员推送通知
- 支持嘟文右到左文字
- 修复过滤整词功能
- 修复平板不能删除草稿

View File

@ -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",

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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()
} }

View File

@ -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
removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''), ? diffChars(
removeHTML(item.spoiler_text || '') removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''),
) removeHTML(item.spoiler_text || '')
const changesContent = diffWords( )
removeHTML(prevItem?.content || item.content), : diffWords(
removeHTML(item.content) removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''),
) 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}
/> />

View File

@ -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': {