feat(post): add post reference functionality with inline cards and suggestion search
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 2m42s
Frontend CI / build-android-apk (push) Has been cancelled
Frontend CI / ota-android (push) Has been cancelled

Add support for referencing/quoting other posts within post content. Includes:
- New `post_ref` segment type with `PostRefSegmentData` interface for post references
- New `PostRefCard` component to display referenced posts inline
- Post suggestion search triggered by `#` in `PostMentionInput`
- Updated `PostContentRenderer` to render post reference segments
- Add `suggestPosts` and `recordRefClick` API methods in postService

Additional improvements:
- Add image gallery preview for user profile covers and avatars
- Make text selectable across CommentItem, PostContentRenderer, and PostDetailScreen
- Migrate FlatList to FlashList in PostDetailScreen and UserProfileScreen
- Add nestedScrollEnabled to CreatePostScreen
- Add clickable member avatars in GroupMembersScreen
This commit is contained in:
lafay
2026-04-26 01:11:30 +08:00
parent 5fa5403d6a
commit 2dc6936aac
11 changed files with 554 additions and 193 deletions

View File

@@ -21,6 +21,7 @@ import {
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { hrefUserProfile } from '../../navigation/hrefs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { blurActiveElement } from '../../infrastructure/platform';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
@@ -383,24 +384,36 @@ const GroupMembersScreen: React.FC = () => {
}
};
// 点击头像进入对方主页
const handleAvatarPress = (member: GroupMemberResponse) => {
if (member.user_id) {
router.push(hrefUserProfile(String(member.user_id)));
}
};
// 渲染成员项
const renderMember: ListRenderItem<GroupMemberResponse> = ({ item }) => {
const isSelf = item.user_id === currentUser?.id;
const canManage = isAdmin && item.role !== 'owner' && (isOwner || item.role === 'member');
return (
<TouchableOpacity
style={styles.memberItem}
onPress={() => canManage ? openActionModal(item) : null}
onLongPress={() => isSelf ? openNicknameModal(item) : null}
activeOpacity={canManage ? 0.7 : 1}
>
<Avatar
source={item.user?.avatar}
size={48}
name={item.user?.nickname || item.nickname}
/>
<View style={styles.memberInfo}>
<View style={styles.memberItem}>
<TouchableOpacity
onPress={() => handleAvatarPress(item)}
activeOpacity={0.7}
>
<Avatar
source={item.user?.avatar}
size={48}
name={item.user?.nickname || item.nickname}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.memberInfo}
onPress={() => isSelf ? openNicknameModal(item) : handleAvatarPress(item)}
onLongPress={() => isSelf ? openNicknameModal(item) : null}
activeOpacity={0.7}
>
<View style={styles.memberNameRow}>
<Text variant="body" style={styles.memberName}>
{item.nickname || item.user?.nickname || '未知用户'}
@@ -422,11 +435,16 @@ const GroupMembersScreen: React.FC = () => {
<Text variant="label" color={colors.error.main}> </Text>
</View>
)}
</View>
</TouchableOpacity>
{canManage && (
<MaterialCommunityIcons name="dots-vertical" size={20} color={colors.text.hint} />
<TouchableOpacity
onPress={() => openActionModal(item)}
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
>
<MaterialCommunityIcons name="dots-vertical" size={20} color={colors.text.hint} />
</TouchableOpacity>
)}
</TouchableOpacity>
</View>
);
};