mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Fixed #118
This commit is contained in:
		| @@ -4,7 +4,7 @@ | ||||
|     "native": "210511", | ||||
|     "major": 2, | ||||
|     "minor": 0, | ||||
|     "patch": 1, | ||||
|     "patch": 2, | ||||
|     "expo": "41.0.0" | ||||
|   }, | ||||
|   "description": "tooot app for Mastodon", | ||||
|   | ||||
							
								
								
									
										9
									
								
								src/@types/mastodon.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								src/@types/mastodon.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -261,6 +261,15 @@ declare namespace Mastodon { | ||||
|     verified_at: string | null | ||||
|   } | ||||
|  | ||||
|   type Filter = { | ||||
|     id: string | ||||
|     phrase: string | ||||
|     context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[] | ||||
|     expires_at?: string | ||||
|     irreversible: boolean | ||||
|     whole_word: boolean | ||||
|   } | ||||
|  | ||||
|   type List = { | ||||
|     id: string | ||||
|     title: string | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import pushUseReceive from '@utils/push/useReceive' | ||||
| import pushUseRespond from '@utils/push/useRespond' | ||||
| import { updatePreviousTab } from '@utils/slices/contextsSlice' | ||||
| import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' | ||||
| import { updateFilters } from '@utils/slices/instances/updateFilters' | ||||
| import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import { themes } from '@utils/styles/themes' | ||||
| @@ -106,6 +107,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => { | ||||
|   // Lazily update users's preferences, for e.g. composing default visibility | ||||
|   useEffect(() => { | ||||
|     if (instanceActive !== -1) { | ||||
|       dispatch(updateFilters()) | ||||
|       dispatch(updateAccountPreferences()) | ||||
|     } | ||||
|   }, [instanceActive]) | ||||
|   | ||||
| @@ -10,14 +10,16 @@ import TimelinePoll from '@components/Timeline/Shared/Poll' | ||||
| import { useNavigation } from '@react-navigation/native' | ||||
| import { StackNavigationProp } from '@react-navigation/stack' | ||||
| import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||
| import { getInstanceAccount } from '@utils/slices/instancesSlice' | ||||
| import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import htmlparser2 from 'htmlparser2-without-node-native' | ||||
| import { uniqBy } from 'lodash' | ||||
| import React, { useCallback } from 'react' | ||||
| import { Pressable, StyleSheet, View } from 'react-native' | ||||
| import { useSelector } from 'react-redux' | ||||
| import TimelineActionsUsers from './Shared/ActionsUsers' | ||||
| import TimelineFiltered, { shouldFilter } from './Shared/Filtered' | ||||
| import TimelineFullConversation from './Shared/FullConversation' | ||||
| import TimelineTranslate from './Shared/Translate' | ||||
|  | ||||
| @@ -49,6 +51,16 @@ const TimelineDefault: React.FC<Props> = ({ | ||||
|  | ||||
|   let actualStatus = item.reblog ? item.reblog : item | ||||
|  | ||||
|   const ownAccount = actualStatus.account.id === instanceAccount?.id | ||||
|  | ||||
|   if ( | ||||
|     !highlighted && | ||||
|     queryKey && | ||||
|     shouldFilter({ status: actualStatus, queryKey }) | ||||
|   ) { | ||||
|     return <TimelineFiltered /> | ||||
|   } | ||||
|  | ||||
|   const onPress = useCallback(() => { | ||||
|     analytics('timeline_default_press', { | ||||
|       page: queryKey ? queryKey[1].page : origin | ||||
| @@ -118,7 +130,7 @@ const TimelineDefault: React.FC<Props> = ({ | ||||
|             statusId={actualStatus.id} | ||||
|             poll={actualStatus.poll} | ||||
|             reblog={item.reblog ? true : false} | ||||
|             sameAccount={actualStatus.account.id === instanceAccount?.id} | ||||
|             sameAccount={ownAccount} | ||||
|           /> | ||||
|         ) : null} | ||||
|         {!disableDetails && | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import { uniqBy } from 'lodash' | ||||
| import React, { useCallback } from 'react' | ||||
| import { Pressable, StyleSheet, View } from 'react-native' | ||||
| import { useSelector } from 'react-redux' | ||||
| import TimelineFiltered, { shouldFilter } from './Shared/Filtered' | ||||
| import TimelineFullConversation from './Shared/FullConversation' | ||||
|  | ||||
| export interface Props { | ||||
| @@ -30,6 +31,13 @@ const TimelineNotifications: React.FC<Props> = ({ | ||||
|   queryKey, | ||||
|   highlighted = false | ||||
| }) => { | ||||
|   if ( | ||||
|     notification.status && | ||||
|     shouldFilter({ status: notification.status, queryKey }) | ||||
|   ) { | ||||
|     return <TimelineFiltered /> | ||||
|   } | ||||
|  | ||||
|   const { theme } = useTheme() | ||||
|   const instanceAccount = useSelector( | ||||
|     getInstanceAccount, | ||||
| @@ -38,6 +46,7 @@ const TimelineNotifications: React.FC<Props> = ({ | ||||
|   const navigation = useNavigation< | ||||
|     StackNavigationProp<Nav.TabLocalStackParamList> | ||||
|   >() | ||||
|  | ||||
|   const actualAccount = notification.status | ||||
|     ? notification.status.account | ||||
|     : notification.account | ||||
|   | ||||
							
								
								
									
										105
									
								
								src/components/Timeline/Shared/Filtered.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/components/Timeline/Shared/Filtered.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| import { store } from '@root/store' | ||||
| import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||
| import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import htmlparser2 from 'htmlparser2-without-node-native' | ||||
| import React from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { Text, View } from 'react-native' | ||||
|  | ||||
| const TimelineFiltered = React.memo( | ||||
|   () => { | ||||
|     const { theme } = useTheme() | ||||
|     const { t } = useTranslation('componentTimeline') | ||||
|  | ||||
|     return ( | ||||
|       <View style={{ backgroundColor: theme.backgroundDefault }}> | ||||
|         <Text | ||||
|           style={{ | ||||
|             ...StyleConstants.FontStyle.S, | ||||
|             color: theme.secondary, | ||||
|             textAlign: 'center', | ||||
|             paddingVertical: StyleConstants.Spacing.S, | ||||
|             paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S | ||||
|           }} | ||||
|         > | ||||
|           {t('shared.filtered')} | ||||
|         </Text> | ||||
|       </View> | ||||
|     ) | ||||
|   }, | ||||
|   () => true | ||||
| ) | ||||
|  | ||||
| export const shouldFilter = ({ | ||||
|   status, | ||||
|   queryKey | ||||
| }: { | ||||
|   status: Mastodon.Status | ||||
|   queryKey: QueryKeyTimeline | ||||
| }) => { | ||||
|   const instance = getInstance(store.getState()) | ||||
|   const ownAccount = | ||||
|     getInstanceAccount(store.getState())?.id === status.account.id | ||||
|  | ||||
|   let shouldFilter = false | ||||
|   if (queryKey && !ownAccount) { | ||||
|     const parser = new htmlparser2.Parser({ | ||||
|       ontext (text: string) { | ||||
|         const checkFilter = (filter: Mastodon.Filter) => { | ||||
|           switch (filter.whole_word) { | ||||
|             case true: | ||||
|               if (new RegExp('\\b' + filter.phrase + '\\b').test(text)) { | ||||
|                 shouldFilter = true | ||||
|               } | ||||
|               break | ||||
|             case false: | ||||
|               if (new RegExp(filter.phrase).test(text)) { | ||||
|                 shouldFilter = true | ||||
|               } | ||||
|               break | ||||
|           } | ||||
|         } | ||||
|         instance?.filters.forEach(filter => { | ||||
|           if (filter.expires_at) { | ||||
|             if (new Date().getTime() > new Date(filter.expires_at).getTime()) { | ||||
|               return | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           switch (queryKey[1].page) { | ||||
|             case 'Following': | ||||
|             case 'Local': | ||||
|             case 'List': | ||||
|             case 'Account_Default': | ||||
|               if (filter.context.includes('home')) { | ||||
|                 checkFilter(filter) | ||||
|               } | ||||
|               break | ||||
|             case 'Notifications': | ||||
|               if (filter.context.includes('notifications')) { | ||||
|                 checkFilter(filter) | ||||
|               } | ||||
|               break | ||||
|             case 'LocalPublic': | ||||
|               if (filter.context.includes('public')) { | ||||
|                 checkFilter(filter) | ||||
|               } | ||||
|               break | ||||
|             case 'Toot': | ||||
|               if (filter.context.includes('thread')) { | ||||
|                 checkFilter(filter) | ||||
|               } | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
|     }) | ||||
|     parser.write(status.content) | ||||
|     parser.end() | ||||
|   } | ||||
|  | ||||
|   return shouldFilter | ||||
| } | ||||
|  | ||||
| export default TimelineFiltered | ||||
| @@ -73,6 +73,7 @@ | ||||
|     "content": { | ||||
|       "expandHint": "hidden content" | ||||
|     }, | ||||
|     "filtered": "Filtered", | ||||
|     "fullConversation": "Read conversations", | ||||
|     "translate": { | ||||
|       "default": "Translate", | ||||
|   | ||||
							
								
								
									
										12
									
								
								src/utils/slices/instances/updateFilters.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/utils/slices/instances/updateFilters.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import apiInstance from '@api/instance' | ||||
| import { createAsyncThunk } from '@reduxjs/toolkit' | ||||
|  | ||||
| export const updateFilters = createAsyncThunk( | ||||
|   'instances/updateFilters', | ||||
|   async (): Promise<Mastodon.Filter[]> => { | ||||
|     return apiInstance<Mastodon.Filter[]>({ | ||||
|       method: 'get', | ||||
|       url: `filters` | ||||
|     }).then(res => res.body) | ||||
|   } | ||||
| ) | ||||
| @@ -6,6 +6,7 @@ import { findIndex } from 'lodash' | ||||
| import addInstance from './instances/add' | ||||
| import removeInstance from './instances/remove' | ||||
| import { updateAccountPreferences } from './instances/updateAccountPreferences' | ||||
| import { updateFilters } from './instances/updateFilters' | ||||
| import { updateInstancePush } from './instances/updatePush' | ||||
| import { updateInstancePushAlert } from './instances/updatePushAlert' | ||||
| import { updateInstancePushDecode } from './instances/updatePushDecode' | ||||
| @@ -29,6 +30,7 @@ export type Instance = { | ||||
|     avatarStatic: Mastodon.Account['avatar_static'] | ||||
|     preferences: Mastodon.Preferences | ||||
|   } | ||||
|   filters: Mastodon.Filter[] | ||||
|   notifications_filter: { | ||||
|     follow: boolean | ||||
|     favourite: boolean | ||||
| @@ -236,6 +238,15 @@ const instancesSlice = createSlice({ | ||||
|         console.error(action.error) | ||||
|       }) | ||||
|  | ||||
|       // Update Instance Account Filters | ||||
|       .addCase(updateFilters.fulfilled, (state, action) => { | ||||
|         const activeIndex = findInstanceActive(state.instances) | ||||
|         state.instances[activeIndex].filters = action.payload | ||||
|       }) | ||||
|       .addCase(updateFilters.rejected, (_, action) => { | ||||
|         console.error(action.error) | ||||
|       }) | ||||
|  | ||||
|       // Update Instance Account Preferences | ||||
|       .addCase(updateAccountPreferences.fulfilled, (state, action) => { | ||||
|         const activeIndex = findInstanceActive(state.instances) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user