feat(message): implement message search functionality
Add ability to search messages within a specific conversation. This includes: - New `MessageSearchScreen` for displaying search results. - `MessageRepository.searchByConversation` to query messages by keyword and conversation ID. - Integration of search entry points in `GroupInfoScreen` and `PrivateChatInfoScreen`. - Support for scrolling to a specific message sequence (`scrollToSeq`) when navigating from search results. - Enhanced `HighlightText` component to support custom highlight styles. feat(message): implement message search functionality
This commit is contained in:
@@ -97,6 +97,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
groupId?: string | string[];
|
||||
groupName?: string | string[];
|
||||
groupAvatar?: string | string[];
|
||||
scrollToSeq?: string | string[];
|
||||
}>();
|
||||
// 路由参数(动态段 + query);群 ID 勿用 Number(),避免超过 2^53-1 时精度丢失
|
||||
// 嵌入式模式下优先使用 props,否则从路由参数获取
|
||||
@@ -112,6 +113,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
};
|
||||
}
|
||||
const isGroupFlag = firstRouteParam(rawParams.isGroupChat);
|
||||
const scrollToSeqParam = firstRouteParam(rawParams.scrollToSeq);
|
||||
return {
|
||||
conversationId: firstRouteParam(rawParams.conversationId) ?? null,
|
||||
userId: firstRouteParam(rawParams.userId) ?? null,
|
||||
@@ -119,6 +121,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
groupId: firstRouteParam(rawParams.groupId),
|
||||
groupName: firstRouteParam(rawParams.groupName),
|
||||
groupAvatar: firstRouteParam(rawParams.groupAvatar),
|
||||
scrollToSeq: scrollToSeqParam ? Number(scrollToSeqParam) : null,
|
||||
};
|
||||
}, [
|
||||
props?.embeddedConversationId,
|
||||
@@ -131,9 +134,10 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
rawParams.groupId,
|
||||
rawParams.groupName,
|
||||
rawParams.groupAvatar,
|
||||
rawParams.scrollToSeq,
|
||||
]);
|
||||
|
||||
const { conversationId: routeConversationId, userId: routeUserId, isGroupChat, groupId: routeGroupId, groupName: routeGroupName, groupAvatar: routeGroupAvatar } = routeParams;
|
||||
const { conversationId: routeConversationId, userId: routeUserId, isGroupChat, groupId: routeGroupId, groupName: routeGroupName, groupAvatar: routeGroupAvatar, scrollToSeq: routeScrollToSeq } = routeParams;
|
||||
|
||||
// 当前会话ID(可能在新建私聊会话后动态更新)
|
||||
const [conversationId, setConversationId] = useState<string | null>(routeConversationId);
|
||||
@@ -200,6 +204,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
const isBrowsingHistoryRef = useRef(false);
|
||||
const hasShownMessageListRef = useRef(false);
|
||||
const historyLoadingLockUntilRef = useRef(0);
|
||||
const scrollToSeqRef = useRef<number | null>(null);
|
||||
|
||||
// 回复消息状态
|
||||
const [replyingTo, setReplyingTo] = useState<GroupMessage | null>(null);
|
||||
@@ -377,6 +382,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
isBrowsingHistoryRef.current = false;
|
||||
hasShownMessageListRef.current = false;
|
||||
historyLoadingLockUntilRef.current = 0;
|
||||
scrollToSeqRef.current = routeScrollToSeq ?? null;
|
||||
}, [conversationId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -415,7 +421,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
return scrollY <= 100;
|
||||
}, [scrollPositionRef]);
|
||||
|
||||
// 首屏加载完成后仅锚定一次到最新消息端
|
||||
// 首屏加载完成后仅锚定一次到最新消息端(有 scrollToSeq 时跳过)
|
||||
useEffect(() => {
|
||||
if (
|
||||
loading ||
|
||||
@@ -427,6 +433,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
return;
|
||||
}
|
||||
hasInitialAnchorDoneRef.current = true;
|
||||
if (scrollToSeqRef.current != null) return; // 由下方 scrollToSeq effect 处理
|
||||
const timer = setTimeout(() => {
|
||||
scrollToLatest(false, true, 'initial-anchor');
|
||||
}, 0);
|
||||
@@ -606,6 +613,36 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
}
|
||||
}, [conversationId, hasMoreHistory, loadingMore, loadMoreMessages, messages]);
|
||||
|
||||
// 从搜索结果跳转:滚动到目标 seq
|
||||
useEffect(() => {
|
||||
const targetSeq = scrollToSeqRef.current;
|
||||
if (targetSeq == null || !hasInitialAnchorDoneRef.current) return;
|
||||
if (loading || loadingMore) return;
|
||||
|
||||
const found = messages.find(m => m.seq === targetSeq);
|
||||
if (found) {
|
||||
scrollToSeqRef.current = null;
|
||||
isBrowsingHistoryRef.current = true;
|
||||
suppressAutoFollowRef.current = true;
|
||||
const ascendingIndex = messages.findIndex(m => m.seq === targetSeq);
|
||||
const displayIndex = messages.length - 1 - ascendingIndex;
|
||||
setTimeout(() => {
|
||||
flatListRef.current?.scrollToIndex({
|
||||
index: displayIndex,
|
||||
animated: false,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasMoreHistory) {
|
||||
loadMoreHistory();
|
||||
} else {
|
||||
scrollToSeqRef.current = null;
|
||||
}
|
||||
}, [loading, loadingMore, messages, hasMoreHistory, loadMoreHistory]);
|
||||
|
||||
// 列表内容尺寸变化:仅同步内容高度,锚点补偿由 loadMoreHistory 统一处理
|
||||
const handleMessageListContentSizeChange = useCallback((contentWidth: number, contentHeight: number) => {
|
||||
scrollPositionRef.current.contentHeight = contentHeight;
|
||||
|
||||
Reference in New Issue
Block a user