mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Basic mentions, hashtags and links highlighting working
This commit is contained in:
		| @@ -17,6 +17,7 @@ | ||||
|     "@react-navigation/native": "^5.8.6", | ||||
|     "@react-navigation/stack": "^5.12.3", | ||||
|     "@reduxjs/toolkit": "^1.4.0", | ||||
|     "autolinker": "^3.14.2", | ||||
|     "expo": "~39.0.4", | ||||
|     "expo-app-auth": "~9.2.0", | ||||
|     "expo-av": "~8.6.0", | ||||
| @@ -58,4 +59,4 @@ | ||||
|     "typescript": "~3.9.2" | ||||
|   }, | ||||
|   "private": true | ||||
| } | ||||
| } | ||||
| @@ -12,7 +12,7 @@ import { StatusBar } from 'expo-status-bar' | ||||
|  | ||||
| import Local from 'src/stacks/Local' | ||||
| import Public from 'src/stacks/Public' | ||||
| import Post from 'src/stacks/Post' | ||||
| import PostRoot from 'src/stacks/PostRoot' | ||||
| import Notifications from 'src/stacks/Notifications' | ||||
| import Me from 'src/stacks/Me' | ||||
|  | ||||
| @@ -36,7 +36,7 @@ export const Index: React.FC = () => { | ||||
|                 case 'Public': | ||||
|                   name = 'globe' | ||||
|                   break | ||||
|                 case 'Post': | ||||
|                 case 'PostRoot': | ||||
|                   name = 'plus' | ||||
|                   break | ||||
|                 case 'Notifications': | ||||
| @@ -60,7 +60,22 @@ export const Index: React.FC = () => { | ||||
|         > | ||||
|           <Tab.Screen name='Local' component={Local} /> | ||||
|           <Tab.Screen name='Public' component={Public} /> | ||||
|           <Tab.Screen name='Post' component={Post} /> | ||||
|           <Tab.Screen | ||||
|             name='PostRoot' | ||||
|             component={PostRoot} | ||||
|             listeners={({ navigation, route }) => ({ | ||||
|               tabPress: e => { | ||||
|                 e.preventDefault() | ||||
|                 const { | ||||
|                   length, | ||||
|                   [length - 1]: last | ||||
|                 } = navigation.dangerouslyGetState().history | ||||
|                 navigation.navigate(last.key.split(new RegExp(/(.*?)-/))[1], { | ||||
|                   screen: 'PostToot' | ||||
|                 }) | ||||
|               } | ||||
|             })} | ||||
|           /> | ||||
|           <Tab.Screen name='Notifications' component={Notifications} /> | ||||
|           <Tab.Screen name='Me' component={Me} /> | ||||
|         </Tab.Navigator> | ||||
|   | ||||
| @@ -2,12 +2,14 @@ import store, { RootState } from 'src/stacks/common/store' | ||||
| import ky from 'ky' | ||||
|  | ||||
| const client = async ({ | ||||
|   version = 'v1', | ||||
|   method, | ||||
|   instance, | ||||
|   endpoint, | ||||
|   query, | ||||
|   body | ||||
| }: { | ||||
|   version: 'v1' | 'v2' | ||||
|   method: 'get' | 'post' | 'delete' | ||||
|   instance: 'local' | 'remote' | ||||
|   endpoint: string | ||||
| @@ -16,13 +18,13 @@ const client = async ({ | ||||
|   } | ||||
|   body?: object | ||||
| }): Promise<any> => { | ||||
|   const state: RootState["instanceInfo"] = store.getState().instanceInfo | ||||
|   const state: RootState['instanceInfo'] = store.getState().instanceInfo | ||||
|  | ||||
|   let response | ||||
|   try { | ||||
|     response = await ky(endpoint, { | ||||
|       method: method, | ||||
|       prefixUrl: `https://${state[instance]}/api/v1`, | ||||
|       prefixUrl: `https://${state[instance]}/api/${version}`, | ||||
|       searchParams: query, | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|   | ||||
| @@ -4,6 +4,8 @@ import { createNativeStackNavigator } from 'react-native-screens/native-stack' | ||||
| import Base from './Me/Base' | ||||
| import Authentication from 'src/stacks/Me/Authentication' | ||||
|  | ||||
| import sharedScreens from 'src/stacks/Shared/sharedScreens' | ||||
|  | ||||
| const Stack = createNativeStackNavigator() | ||||
|  | ||||
| const Me: React.FC = () => { | ||||
| @@ -17,6 +19,8 @@ const Me: React.FC = () => { | ||||
|           stackPresentation: 'modal' | ||||
|         }} | ||||
|       /> | ||||
|  | ||||
|       {sharedScreens(Stack)} | ||||
|     </Stack.Navigator> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,23 +0,0 @@ | ||||
| import React from 'react' | ||||
| import { View } from 'react-native' | ||||
| import { createNativeStackNavigator } from 'react-native-screens/native-stack' | ||||
|  | ||||
| const Stack = createNativeStackNavigator() | ||||
|  | ||||
| const Post: React.FC = () => { | ||||
|   return ( | ||||
|     // <Stack.Navigator> | ||||
|     //   <Stack.Screen name='Me-Base' component={Base} /> | ||||
|     //   <Stack.Screen | ||||
|     //     name='Me-Authentication' | ||||
|     //     component={Authentication} | ||||
|     //     options={{ | ||||
|     //       stackPresentation: 'modal' | ||||
|     //     }} | ||||
|     //   /> | ||||
|     // </Stack.Navigator> | ||||
|     <View></View> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default Post | ||||
							
								
								
									
										7
									
								
								src/stacks/PostRoot.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/stacks/PostRoot.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import React from 'react' | ||||
|  | ||||
| const PostRoot: React.FC = () => { | ||||
|   return <></> | ||||
| } | ||||
|  | ||||
| export default PostRoot | ||||
							
								
								
									
										128
									
								
								src/stacks/Shared/PostToot.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/stacks/Shared/PostToot.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| import { Feather } from '@expo/vector-icons' | ||||
| import React, { useCallback, useEffect, useState } from 'react' | ||||
| import { | ||||
|   Keyboard, | ||||
|   Pressable, | ||||
|   StyleSheet, | ||||
|   Text, | ||||
|   TextInput, | ||||
|   View | ||||
| } from 'react-native' | ||||
| import { createNativeStackNavigator } from 'react-native-screens/native-stack' | ||||
| import Autolinker, { HtmlTag } from 'autolinker' | ||||
|  | ||||
| const Stack = createNativeStackNavigator() | ||||
|  | ||||
| const PostTootMain = () => { | ||||
|   const [viewHeight, setViewHeight] = useState(0) | ||||
|   const [keyboardHeight, setKeyboardHeight] = useState(0) | ||||
|   useEffect(() => { | ||||
|     Keyboard.addListener('keyboardDidShow', _keyboardDidShow) | ||||
|     Keyboard.addListener('keyboardDidHide', _keyboardDidHide) | ||||
|  | ||||
|     // cleanup function | ||||
|     return () => { | ||||
|       Keyboard.removeListener('keyboardDidShow', _keyboardDidShow) | ||||
|       Keyboard.removeListener('keyboardDidHide', _keyboardDidHide) | ||||
|     } | ||||
|   }) | ||||
|   const _keyboardDidShow = (props: any) => { | ||||
|     setKeyboardHeight(props.endCoordinates.height) | ||||
|   } | ||||
|  | ||||
|   const _keyboardDidHide = () => { | ||||
|     setKeyboardHeight(0) | ||||
|   } | ||||
|  | ||||
|   const [charCount, setCharCount] = useState(0) | ||||
|   const [formattedText, setFormattedText] = useState<React.ReactNode>() | ||||
|   const onChangeText = useCallback(content => { | ||||
|     const tags: string[] = [] | ||||
|     Autolinker.link(content, { | ||||
|       email: false, | ||||
|       phone: false, | ||||
|       mention: 'twitter', | ||||
|       hashtag: 'twitter', | ||||
|       replaceFn: props => { | ||||
|         const tag = props.getMatchedText() | ||||
|         tags.push(tag) | ||||
|         return tag | ||||
|       } | ||||
|     }) | ||||
|  | ||||
|     let _content = content | ||||
|     const children = [] | ||||
|     tags.forEach(tag => { | ||||
|       const parts = _content.split(tag) | ||||
|       children.push(parts.shift()) | ||||
|       children.push(<Text style={{ color: 'red' }}>{tag}</Text>) | ||||
|       _content = parts.join(tag) | ||||
|     }) | ||||
|     children.push(_content) | ||||
|  | ||||
|     setFormattedText(React.createElement(Text, null, children)) | ||||
|     setCharCount(content.length) | ||||
|   }, []) | ||||
|  | ||||
|   return ( | ||||
|     <View | ||||
|       style={styles.main} | ||||
|       onLayout={({ nativeEvent }) => { | ||||
|         setViewHeight(nativeEvent.layout.height) | ||||
|       }} | ||||
|     > | ||||
|       <TextInput | ||||
|         style={[styles.textInput, { height: viewHeight - keyboardHeight - 44 }]} | ||||
|         autoCapitalize='none' | ||||
|         autoFocus | ||||
|         enablesReturnKeyAutomatically | ||||
|         multiline | ||||
|         placeholder='想说点什么' | ||||
|         // value={rawText} | ||||
|         onChangeText={onChangeText} | ||||
|         scrollEnabled | ||||
|       > | ||||
|         <Text>{formattedText}</Text> | ||||
|       </TextInput> | ||||
|       <Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}> | ||||
|         <Feather name='paperclip' size={24} /> | ||||
|         <Feather name='bar-chart-2' size={24} /> | ||||
|         <Feather name='eye-off' size={24} /> | ||||
|         <Text>{charCount}</Text> | ||||
|       </Pressable> | ||||
|     </View> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const PostToot: React.FC = () => { | ||||
|   return ( | ||||
|     <Stack.Navigator> | ||||
|       <Stack.Screen | ||||
|         name='PostTootMain' | ||||
|         component={PostTootMain} | ||||
|         options={{ | ||||
|           headerLeft: () => <Text>取消</Text>, | ||||
|           headerCenter: () => <></>, | ||||
|           headerRight: () => <Text>发嘟嘟</Text> | ||||
|         }} | ||||
|       /> | ||||
|     </Stack.Navigator> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   main: { | ||||
|     width: '100%', | ||||
|     height: '100%' | ||||
|   }, | ||||
|   textInput: { | ||||
|     backgroundColor: 'gray' | ||||
|   }, | ||||
|   additions: { | ||||
|     height: 44, | ||||
|     backgroundColor: 'red', | ||||
|     flexDirection: 'row' | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default PostToot | ||||
| @@ -4,6 +4,7 @@ import Account from 'src/stacks/Shared/Account' | ||||
| import Hashtag from 'src/stacks/Shared/Hashtag' | ||||
| import Toot from 'src/stacks/Shared/Toot' | ||||
| import Webview from 'src/stacks/Shared/Webview' | ||||
| import PostToot from './PostToot' | ||||
|  | ||||
| const sharedScreens = (Stack: any) => { | ||||
|   return [ | ||||
| @@ -21,7 +22,7 @@ const sharedScreens = (Stack: any) => { | ||||
|       key='Hashtag' | ||||
|       name='Hashtag' | ||||
|       component={Hashtag} | ||||
|       options={({ route }) => ({ | ||||
|       options={({ route }: any) => ({ | ||||
|         title: `#${decodeURIComponent(route.params.hashtag)}` | ||||
|       })} | ||||
|     />, | ||||
| @@ -40,6 +41,14 @@ const sharedScreens = (Stack: any) => { | ||||
|       // options={({ route }) => ({ | ||||
|       //   title: `${route.params.domain}` | ||||
|       // })} | ||||
|     />, | ||||
|     <Stack.Screen | ||||
|       key='PostToot' | ||||
|       name='PostToot' | ||||
|       component={PostToot} | ||||
|       options={{ | ||||
|         stackPresentation: 'modal' | ||||
|       }} | ||||
|     /> | ||||
|   ] | ||||
| } | ||||
|   | ||||
							
								
								
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -1670,6 +1670,13 @@ atob@^2.1.2: | ||||
|   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" | ||||
|   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== | ||||
|  | ||||
| autolinker@^3.14.2: | ||||
|   version "3.14.2" | ||||
|   resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-3.14.2.tgz#71856274eb768fb7149039e24d3a2be2f5c55a63" | ||||
|   integrity sha512-VO66nXUCZFxTq7fVHAaiAkZNXRQ1l3IFi6D5P7DLoyIEAn2E8g7TWbyEgLlz1uW74LfWmu1A17IPWuPQyGuNVg== | ||||
|   dependencies: | ||||
|     tslib "^1.9.3" | ||||
|  | ||||
| available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: | ||||
|   version "1.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" | ||||
| @@ -5791,6 +5798,11 @@ toidentifier@1.0.0: | ||||
|   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" | ||||
|   integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== | ||||
|  | ||||
| tslib@^1.9.3: | ||||
|   version "1.14.1" | ||||
|   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" | ||||
|   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== | ||||
|  | ||||
| type-fest@^0.7.1: | ||||
|   version "0.7.1" | ||||
|   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user